/////////////////////////////////////////////////////////////////////////////
// Name:        epub_utils.cpp
// Purpose:     Epub utilities
// Author:      Julian Smart
// Modified by:
// Created:     2008-11-18
// RCS-ID:      $Id$
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#include "wx/wx.h"

#include "wx/datstrm.h"
#include "wx/arrimpl.cpp"
#include "wx/wfstream.h"
#include "wx/txtstrm.h"
#include "wx/zipstrm.h"
#include "wx/textfile.h"
#include "wx/intl.h"
#include "wx/xrc/xmlres.h"
#include "wx/dir.h"

#include "fileutils/fileutils.h"
#include "epubutils/epub_utils.h"

static void ebConvertTextToHTML(wxString& str);
wxString ebTextToHTML(const wxString& str)
{
    wxString s(str);
    ebConvertTextToHTML(s);
    return s;
}

WX_DEFINE_OBJARRAY(ebHtmlFileArray)

/*
 * Basic information about a book
 */

ebBasicInfo::ebBasicInfo()
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_title.SetDisplayName(wxTRANSLATE("Title"));
    m_title.SetName(wxT("dc:title"));

    m_id.SetDisplayName(wxTRANSLATE("Identifier"));
    m_id.SetName(wxT("dc:identifier"));
    m_id.InitializeInstances();
    m_id.GetInstance(0).SetAttributeValue(wxT("id"), wxT("BookId"));

    m_language.SetDisplayName(wxTRANSLATE("Language"));
    m_language.SetName(wxT("dc:language"));

    m_author.SetDisplayName(wxTRANSLATE("Author"));
    m_author.SetName(wxT("dc:creator"));

    m_contributors.SetDisplayName(wxTRANSLATE("Contributors"));
    m_contributors.SetName(wxT("dc:contributor"));

    m_publisher.SetDisplayName(wxTRANSLATE("Publisher"));
    m_publisher.SetName(wxT("dc:publisher"));

    m_url.SetDisplayName(wxTRANSLATE("URL"));
    m_url.SetName(wxT("")); // empty - not to be used as an element on its own

    m_subject.SetDisplayName(wxTRANSLATE("Subject"));
    m_subject.SetName(wxT("dc:subject"));

    m_description.SetDisplayName(wxTRANSLATE("Description"));
    m_description.SetName(wxT("dc:description"));

    m_date.SetDisplayName(wxTRANSLATE("Date"));
    m_date.SetName(wxT("dc:date"));

    m_rights.SetDisplayName(wxTRANSLATE("Rights"));
    m_rights.SetName(wxT("dc:rights"));

    m_coverage.SetDisplayName(wxTRANSLATE("Coverage"));
    m_coverage.SetName(wxT("dc:coverage"));

    m_type.SetDisplayName(wxTRANSLATE("Type"));
    m_type.SetName(wxT("dc:type"));

    m_format.SetDisplayName(wxTRANSLATE("Format"));
    m_format.SetName(wxT("dc:format"));

    m_source.SetDisplayName(wxTRANSLATE("Source"));
    m_source.SetName(wxT("dc:source"));

    m_relation.SetDisplayName(wxTRANSLATE("Relation"));
    m_relation.SetName(wxT("dc:relation"));
#endif
}

void ebBasicInfo::Copy(const ebBasicInfo& info)
{
    m_title = info.m_title;
    m_id = info.m_id;
    m_language = info.m_language;
    m_author = info.m_author;
    m_contributors = info.m_contributors;
    m_publisher = info.m_publisher;
    m_subject = info.m_subject;
    m_description = info.m_description;
    m_date = info.m_date;
    m_rights = info.m_rights;
    m_coverage = info.m_coverage;
    m_type = info.m_type;
    m_format = info.m_format;
    m_source = info.m_source;
    m_relation = info.m_relation;
    m_url = info.m_url;
}

wxString ebBasicInfo::GetAuthor() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_author.GetDefaultValue();
#else
    return m_author;
#endif
}

void ebBasicInfo::SetAuthor(const wxString& author)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_author.SetDefaultValue(author);
#else
    m_author = author;
#endif
}

wxString ebBasicInfo::GetTitle() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_title.GetCombinedValue(wxT(" or"), false);
#else
    return m_title;
#endif
}

void ebBasicInfo::SetTitle(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_title.SetDefaultValue(value);
#else
    m_title = value;
#endif
}

wxString ebBasicInfo::GetId() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_id.GetDefaultValue();
#else
    return m_id;
#endif
}

void ebBasicInfo::SetId(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_id.SetDefaultValue(value);
#else
    m_id = value;
#endif
}

wxString ebBasicInfo::GetPublisher() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_publisher.GetCombinedValue();
#else
    return m_publisher;
#endif
}

void ebBasicInfo::SetPublisher(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_publisher.SetDefaultValue(value);
#else
    m_publisher = value;
#endif
}

wxString ebBasicInfo::GetLanguage() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_language.GetDefaultValue();
#else
    return m_language;
#endif
}

void ebBasicInfo::SetLanguage(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_language.SetDefaultValue(value);
#else
    m_language = value;
#endif
}

wxString ebBasicInfo::GetDescription() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_description.GetCombinedValue(wxT("\n"), false);
#else
    return m_description;
#endif
}

void ebBasicInfo::SetDescription(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_description.SetDefaultValue(value);
#else
    m_description = value;
#endif
}

wxString ebBasicInfo::GetContributors() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_contributors.GetCombinedValue();
#else
    return m_contributors;
#endif
}

void ebBasicInfo::SetContributors(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_contributors.SetDefaultValue(value);
#else
    m_contributors = value;
#endif
}


wxString ebBasicInfo::GetUrl() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_url.GetDefaultValue();
#else
    return m_url;
#endif
}

void ebBasicInfo::SetUrl(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_url.SetDefaultValue(value);
#else
    m_url = value;
#endif
}

wxString ebBasicInfo::GetSubject() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_subject.GetCombinedValue();
#else
    return m_subject;
#endif
}

void ebBasicInfo::SetSubject(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_subject.SetDefaultValue(value);
#else
    m_subject = value;
#endif
}

wxString ebBasicInfo::GetDate() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_date.GetDefaultValue();
#else
    return m_date;
#endif
}

void ebBasicInfo::SetDate(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_date.SetDefaultValue(value);
#else
    m_date = value;
#endif
}


wxString ebBasicInfo::GetRights() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_rights.GetCombinedValue();
#else
    return m_rights;
#endif
}

void ebBasicInfo::SetRights(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_rights.SetDefaultValue(value);
#else
    m_rights = value;
#endif
}

wxString ebBasicInfo::GetCoverage() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_coverage.GetCombinedValue();
#else
    return m_coverage;
#endif
}

void ebBasicInfo::SetCoverage(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_coverage.SetDefaultValue(value);
#else
    m_coverage = value;
#endif
}

wxString ebBasicInfo::GetFormat() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_format.GetDefaultValue();
#else
    return m_format;
#endif
}

void ebBasicInfo::SetFormat(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_format.SetDefaultValue(value);
#else
    m_format = value;
#endif
}

wxString ebBasicInfo::GetType() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_type.GetDefaultValue();
#else
    return m_type;
#endif
}

void ebBasicInfo::SetType(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_type.SetDefaultValue(value);
#else
    m_type = value;
#endif
}

wxString ebBasicInfo::GetSource() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_source.GetCombinedValue();
#else
    return m_source;
#endif
}

void ebBasicInfo::SetSource(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_source.SetDefaultValue(value);
#else
    m_source = value;
#endif
}

wxString ebBasicInfo::GetRelation() const
{
#if ebUSE_COMPLEX_ATTRIBUTES
    return m_relation.GetCombinedValue(wxT(","), false);
#else
    return m_relation;
#endif
}

void ebBasicInfo::SetRelation(const wxString& value)
{
#if ebUSE_COMPLEX_ATTRIBUTES
    m_relation.SetDefaultValue(value);
#else
    m_relation = value;
#endif
}


/*
 * Epub generation options
 */

void ebEpubOptions::Init()
{
    m_generateTitlePage = false;
    m_generateTOC = true;
    m_generateGuide = true;
    m_generateCoverPage = true;
    m_generateCSS = false;
    m_expandCoverImage = true;
    m_allowHtmlInText = false;
    m_takeTitleFromFirstLine = true;
    m_importEncoding = wxT("UTF-8");
    m_generateEpub = true;
    m_generateMobiPocket = false;
    m_useRTL = false;

    // CSS
    m_firstLineIndent = 10;
    m_paragraphSpacing = 10;

    m_compressionLevel = -1;
}

void ebEpubOptions::Copy(const ebEpubOptions& options)
{
    m_generateTitlePage = options.m_generateTitlePage;
    m_generateTOC = options.m_generateTOC;
    m_generateGuide = options.m_generateGuide;
    m_generateCoverPage = options.m_generateCoverPage;
    m_generateCSS = options.m_generateCSS;
    m_expandCoverImage = options.m_expandCoverImage;
    m_allowHtmlInText = options.m_allowHtmlInText;
    m_takeTitleFromFirstLine = options.m_takeTitleFromFirstLine;
    m_importEncoding = options.m_importEncoding;
    m_generateEpub = options.m_generateEpub;
    m_generateMobiPocket = options.m_generateMobiPocket;
    m_useRTL = options.m_useRTL;

    // CSS
    m_firstLineIndent = options.m_firstLineIndent;
    m_paragraphSpacing = options.m_paragraphSpacing;

    m_compressionLevel = options.m_compressionLevel;
}

/*
 * HTML content file storage
 */

void ebHtmlFile::Init()
{
    m_showInTOC = true;
    m_level = 1;
    m_guideType = wxT("<none>");
}

void ebHtmlFile::Copy(const ebHtmlFile& f)
{
    m_fileLocation = f.m_fileLocation;
    m_guideType = f.m_guideType;
    m_showInTOC = f.m_showInTOC;
    m_tocTitle = f.m_tocTitle;
    m_level = f.m_level;
}

// Is this an HTML file? If not, we assume it's a plain text file.
bool ebHtmlFile::IsHtmlFile() const
{
    wxString ext = FileUtilities::GetExtension(m_fileLocation).Lower();
    if (ext == wxT("html") || ext == wxT("htm") || ext == wxT("xml") || ext == wxT("xhtml"))
        return true;
    else
        return false;
}

// Is this a plain text file (extension txt)?
bool ebHtmlFile::IsTextFile() const
{
    wxString ext = FileUtilities::GetExtension(m_fileLocation).Lower();
    if (ext == wxT("txt"))
        return true;
    else
        return false;
}

// Return an absolute HTML file, even if it's a text file.
wxString ebHtmlFile::GetAbsoluteHtmlFile(const ebFileLocations& locations) const
{
    wxString filename = m_fileLocation;
    if (!IsHtmlFile())
    {
        FileUtilities::StripExtension(filename);
        filename += wxT(".html");
    }
    if (!wxIsAbsolutePath(filename))
        filename = FileUtilities::AppendPaths(locations.m_projectFolder, filename);
    return filename;
}

// Return an absolute file
wxString ebHtmlFile::GetAbsoluteFile(const ebFileLocations& locations) const
{
    wxString filename = m_fileLocation;
    if (!wxIsAbsolutePath(filename))
        filename = FileUtilities::AppendPaths(locations.m_projectFolder, filename);
    return filename;
}

// Return a relative HTML file, even if it's a text file.
wxString ebHtmlFile::GetRelativeHtmlFile(const ebFileLocations& locations) const
{
    wxString filename(GetRelativeFile(locations));
    if (!IsHtmlFile())
    {
        FileUtilities::StripExtension(filename);
        filename += wxT(".html");
    }
    return filename;
}

// Return a relative file
wxString ebHtmlFile::GetRelativeFile(const ebFileLocations& locations) const
{
    return ebMakeRelative(m_fileLocation, locations.m_projectFolder);
}


/*
 * File locations
 */

ebFileLocations::ebFileLocations()
{
}

void ebFileLocations::Copy(const ebFileLocations& info, bool exceptHtmlFiles)
{
    m_projectFolder = info.m_projectFolder;
    m_workingFolder = info.m_workingFolder;
    m_destinationFile = info.m_destinationFile;
    m_cssFile = info.m_cssFile;
    m_coverImageFile = info.m_coverImageFile;

    if (!exceptHtmlFiles)
        m_htmlFiles = info.m_htmlFiles;
}

wxString ebFileLocations::GetAbsoluteLocation(const wxString& path) const
{
    if (wxIsAbsolutePath(path) || path.IsEmpty())
        return path;

    return FileUtilities::AppendPaths(m_projectFolder, path);
}

// Find the index of the HTML filename
int ebFileLocations::FindHtmlFileByFilename(const wxString& filename)
{
    wxString f(filename);
    if (wxIsAbsolutePath(f))
        f = ebMakeRelative(f, m_projectFolder);
    f.Replace(wxT("\\"), wxT("/"));

    size_t i;
    for (i = 0; i < m_htmlFiles.GetCount(); i++)
    {
        wxString f2(m_htmlFiles[i].GetRelativeHtmlFile(* this));
        f2.Replace(wxT("\\"), wxT("/"));
        if (f2 == f)
            return i;
    }
    return -1;
}

/*
 * Generated content information
 */

void ebGeneratedContentInfo::Init()
{
    m_tocFilename = wxT("TableOfContents.html");
    m_tocTitle = wxT("Table of Contents"); // not translated since we may use string table

    m_coverPageFilename = wxT("CoverPage.html");
    m_coverPageTitle = wxT("Cover Page"); // not translated since we may use string table

    m_titlePageFilename = wxT("TitlePage.html");
    m_titlePageTitle = wxEmptyString; // Not used - assume is the same as the book title
}

void ebGeneratedContentInfo::Copy(const ebGeneratedContentInfo& content)
{
    m_tocFilename = content.m_tocFilename;
    m_tocTitle = content.m_tocTitle;

    m_coverPageFilename = content.m_coverPageFilename;
    m_coverPageTitle = content.m_coverPageTitle;

    m_titlePageFilename = content.m_titlePageFilename;
    m_titlePageTitle = content.m_titlePageTitle;
}

/*
 * .epub generator
 */

void ebEpubGenerator::Init()
{
    m_verbosity = ebEPUB_OUTPUT_VERBOSE;
    m_stringTable = NULL;
}

void ebEpubGenerator::Copy(const ebEpubGenerator& gen)
{
    m_locations = gen.m_locations;
    m_basicInfo = gen.m_basicInfo;
    m_options = gen.m_options;

    m_log = gen.m_log;
    m_verbosity = gen.m_verbosity;
    
    m_relativeZipFiles = gen.m_relativeZipFiles;
    m_absoluteZipFiles = gen.m_absoluteZipFiles;

    m_imageFiles = gen.m_imageFiles;

    m_mobigenCommand = gen.m_mobigenCommand;
}

// Generate the .epub
bool ebEpubGenerator::GenerateEpub()
{
    return DoGenerate();
}

// Helper function
bool ebEpubGenerator::DoGenerate()
{
    Clear();

    if (!InitialChecks())
    {
        return false;
    }

    if (!GetImageFiles())
        return false;

    if (!CreateMimeTypeFile())
        return false;

    if (!CreateContainerFile())
        return false;

    if (!CreateNCXFile())
        return false;

    if (!CreateCoverPageFile())
        return false;

    if (!CreateTitlePageFile())
        return false;

    if (!CreateHTMLTOCFile())
        return false;

    if (!CopyImageFiles())
        return false;

    if (!CreateCSSFile())
        return false;

    if (!CreateHtmlFiles())
        return false;

    // Copy other files that might be relevant
    if (!CopyOtherFiles())
        return false;

    if (!CreateContentFile())
        return false;

    if (!CreateEpubFile())
        return false;

    if (!CreateMobiPocketFile())
        return false;

    return true;
}

// Do initial checks
bool ebEpubGenerator::InitialChecks()
{
    if (m_locations.m_projectFolder.IsEmpty() || !wxDirExists(m_locations.m_projectFolder))
    {
        Log(_("Missing project folder."));
        return false;
    }

    if (!wxIsAbsolutePath(m_locations.m_projectFolder))
    {
        Log(_("Project folder must be an absolute path."));
        return false;
    }

    if (m_locations.m_workingFolder.IsEmpty())
    {
        Log(_("Missing working folder."));
        return false;
    }

    if (!wxIsAbsolutePath(m_locations.m_workingFolder))
    {
        m_locations.m_workingFolder = AppendPaths(m_locations.m_projectFolder, m_locations.m_workingFolder);
    }

    if (!wxDirExists(m_locations.m_workingFolder))
    {
        wxMkdir(m_locations.m_workingFolder);
    }

    if (!wxDirExists(m_locations.m_workingFolder))
    {
        Log(_("Could not create working folder."));
        return false;
    }

    if (!wxIsAbsolutePath(m_locations.m_destinationFile))
    {
        m_locations.m_destinationFile = AppendPaths(m_locations.m_projectFolder, m_locations.m_destinationFile);
    }

    if (!m_locations.m_cssFile.IsEmpty() && !wxIsAbsolutePath(m_locations.m_cssFile))
    {
        m_locations.m_cssFile = AppendPaths(m_locations.m_projectFolder, m_locations.m_cssFile);
    }

    wxString contentFolder = GetContentFolder();

    if (!wxDirExists(contentFolder))
    {
        wxMkdir(contentFolder);
    }

    if (!wxDirExists(contentFolder))
    {
        Log(_("Could not create content folder."));
        return false;
    }

    if (m_options.m_generateCoverPage && m_locations.m_coverImageFile.IsEmpty())
        m_locations.m_coverImageFile = GetCoverPageFilename();

    if (!m_locations.m_coverImageFile.IsEmpty() && !wxIsAbsolutePath(m_locations.m_coverImageFile))
    {
        m_locations.m_coverImageFile = AppendPaths(m_locations.m_projectFolder, m_locations.m_coverImageFile);
    }

#if 0
    if (!m_locations.m_cssFile.IsEmpty() && !wxFileExists(m_locations.m_cssFile))
    {
        Log(_("Could not find CSS file."));
        return false;
    }

    if (!m_locations.m_coverImageFile.IsEmpty() && m_options.m_generateCoverPage && !wxFileExists(m_locations.m_coverImageFile))
    {
        Log(_("Could not find cover image file."));
        return false;
    }
#endif

    if (m_locations.m_htmlFiles.GetCount() == 0)
    {
        Log(_("There are no content XHTML files."));
        return false;
    }

    return true;
}

// Write a log string
void ebEpubGenerator::Log(const wxString& msg, int minLevel)
{
    if (m_verbosity >= minLevel)
    {
        m_log += msg;
        m_log += wxT("\n");
    }
}

// Clear all
void ebEpubGenerator::Clear()
{
    ClearLog();

    m_relativeZipFiles.Clear();
    m_absoluteZipFiles.Clear();
    m_imageFiles.Clear();
}

// Create MIME type file mimetype
bool ebEpubGenerator::CreateMimeTypeFile()
{
    wxString mimeTypeFileRelative = wxT("mimetype");
    wxString mimeTypeFileAbsolute = AppendPaths(m_locations.m_workingFolder, mimeTypeFileRelative);

    wxFileOutputStream stream(mimeTypeFileAbsolute);
    if (stream.Ok())
    {
        wxTextOutputStream textStream(stream);
        textStream << wxT("application/epub+zip");

        m_relativeZipFiles.Add(mimeTypeFileRelative);
        m_absoluteZipFiles.Add(mimeTypeFileAbsolute);
    }
    else
    {
        Log(wxString::Format(_("Could not create %s."), mimeTypeFileAbsolute.c_str()));
        return false;
    }

    return true;
}

// Create container file container.xml
bool ebEpubGenerator::CreateContainerFile()
{
    if (!wxDirExists(AppendPaths(m_locations.m_workingFolder, wxT("META-INF"))))
    {
        wxMkdir(AppendPaths(m_locations.m_workingFolder, wxT("META-INF")));
    }

    if (!wxDirExists(AppendPaths(m_locations.m_workingFolder, wxT("META-INF"))))
    {
        Log(wxString(_("Could not create META-INF folder.")));
    }

    wxString containerFileRelative = wxT("META-INF/container.xml");
    wxString containerFileAbsolute = AppendPaths(m_locations.m_workingFolder, containerFileRelative);

    wxFileOutputStream stream(containerFileAbsolute);
    if (stream.Ok())
    {
        wxTextOutputStream textStream(stream);
        textStream << wxT("<?xml version=\"1.0\" ?>\n");
        textStream << wxT("<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n");
        textStream << wxT("   <rootfiles>\n");
        textStream << wxT("      <rootfile full-path=\"OPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n");
        textStream << wxT("   </rootfiles>\n");
        textStream << wxT("</container>\n");

        if (m_relativeZipFiles.Index(containerFileRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(containerFileRelative);
            m_absoluteZipFiles.Add(containerFileAbsolute);
        }
    }
    else
    {
        Log(wxString::Format(_("Could not create %s."), containerFileAbsolute.c_str()));
        return false;
    }

    return true;
}

// Create content.opf
bool ebEpubGenerator::CreateContentFile()
{
    wxString contentFileRelative = wxT("OPS/content.opf");
    wxString contentFileAbsolute = AppendPaths(m_locations.m_workingFolder, contentFileRelative);

    wxFileOutputStream stream(contentFileAbsolute);
    if (stream.Ok())
    {
        wxString subject = m_basicInfo.GetSubject();
        wxString title = m_basicInfo.GetTitle();
        wxString language = m_basicInfo.GetLanguage();
        wxString relation = m_basicInfo.GetRelation();
        wxString description = m_basicInfo.GetDescription();
        wxString creator = m_basicInfo.GetAuthor();
        wxString publisher = m_basicInfo.GetPublisher();
        wxString date = m_basicInfo.GetDate();
        wxString rights = m_basicInfo.GetRights();
        wxString identifier = m_basicInfo.GetId();
        wxString url = m_basicInfo.GetUrl();

        if (subject.IsEmpty())
            subject = wxT("General Fiction");

        if (date.IsEmpty())
        {
            wxDateTime dateTime(wxDateTime::Now());
            date = dateTime.FormatISODate();
        }

        if (relation.IsEmpty())
            relation = m_basicInfo.GetUrl();
        if (language.IsEmpty())
            language = wxT("en");
        if (identifier.IsEmpty())
        {
            if (!url.IsEmpty())
                identifier = url + wxT("/");
            else if (publisher.IsEmpty())
                identifier = publisher + wxT(" ");
            identifier += title;
        }

        ebConvertTextToHTML(subject);
        ebConvertTextToHTML(title);
        ebConvertTextToHTML(language);
        ebConvertTextToHTML(relation);
        ebConvertTextToHTML(description);
        ebConvertTextToHTML(creator);
        ebConvertTextToHTML(publisher);
        ebConvertTextToHTML(date);
        ebConvertTextToHTML(rights);
        ebConvertTextToHTML(identifier);

        wxTextOutputStream textStream(stream);
        textStream << wxT("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");

        wxString bookId(m_basicInfo.m_id.GetAttributeValue(wxT("id")));
        if (bookId.IsEmpty())
        {
            // Make sure the identifier 'id' attribute exists
            bookId = wxT("BookId");
            m_basicInfo.m_id.SetAttributeValue(wxT("id"), bookId);
        }

        textStream << wxT("<package xmlns=\"http://www.idpf.org/2007/opf\" unique-identifier=\"") << bookId << wxT("\" version=\"2.0\">\n");

        textStream << wxT("    <metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:dcterms=\"http://purl.org/dc/terms/\" ");
        textStream << wxT("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
        textStream << wxT("xmlns:opf=\"http://www.idpf.org/2007/opf\">\n");

        wxString indent = wxT("      ");

        textStream << m_basicInfo.m_title.GenerateXML(indent, title);
        textStream << m_basicInfo.m_author.GenerateXML(indent, creator);
        textStream << m_basicInfo.m_contributors.GenerateXML(indent);
        textStream << m_basicInfo.m_language.GenerateXML(indent, language);
        textStream << m_basicInfo.m_id.GenerateXML(indent, identifier);
        textStream << m_basicInfo.m_subject.GenerateXML(indent, subject);
        textStream << m_basicInfo.m_description.GenerateXML(indent, description);
        textStream << m_basicInfo.m_relation.GenerateXML(indent, relation);
        textStream << m_basicInfo.m_publisher.GenerateXML(indent, publisher);
        textStream << m_basicInfo.m_date.GenerateXML(indent, date);
        textStream << m_basicInfo.m_rights.GenerateXML(indent, rights);
        textStream << m_basicInfo.m_relation.GenerateXML(indent, relation);

        ebGeneratedContentInfo generatedInfo;
        wxString coverPageId = MakeIdFromFilename(generatedInfo.m_coverPageFilename);

        // This is for MobiPocket as defined here: http://www.mobipocket.com/dev/article.asp?BaseFolder=prcgen&File=cover.htm
        if (!m_locations.m_coverImageFile.IsEmpty())
        {
            wxString filename(ebMakeRelative(m_locations.m_coverImageFile, m_locations.m_projectFolder));
            wxString id(MakeIdFromFilename(filename));

            textStream << wxT("        <meta name=\"cover\" content=\"") << id << wxT("\"/>\n");
        }

        textStream << wxT("    </metadata>\n");

        textStream << wxT("    <manifest>\n");

        wxString ncxFile;
        wxArrayString htmlFiles;

        size_t i;
        for (i = 0; i < m_relativeZipFiles.GetCount(); i++)
        {
            wxString filename(m_relativeZipFiles[i]);
            // Remove first part of path
            filename.Replace(wxT("OPS/"), wxEmptyString);
#ifdef __WXMSW__
            filename.Replace(wxT("\\"), wxT("/"));
#endif

            wxString id(MakeIdFromFilename(filename));

            wxString mediaType;

            if (filename != wxT("mimetype") && filename != wxT("META-INF/container.xml") && filename != wxT("content.opf"))
            {
                wxString ext = wxFileName(filename).GetExt().Lower();
                if (ext == wxT("ncx"))
                {
                    mediaType = wxT("application/x-dtbncx+xml");
                    ncxFile = filename;
                }
                else if (ext == wxT("xpgt"))
                    mediaType = wxT("application/vnd.adobe-page-template+xml");
                else if (ext == wxT("css"))
                    mediaType = wxT("text/css");
                else if (ext == wxT("html") || ext == wxT("htm") || ext == wxT("xhtml") || ext == wxT("xml"))
                {
                    mediaType = wxT("application/xhtml+xml");
                    htmlFiles.Add(filename);
                }
                else if (ext == wxT("svg"))
                {
                    mediaType = wxT("image/svg+xml");
                    htmlFiles.Add(filename);
                }
                else if (wxImageBlock::DetermineImageType(filename) != -1)
                {
                    wxString ext = wxFileName(filename).GetExt().Lower();
                    wxString imageType;
                    if (ext == wxT("jpg"))
                        imageType = wxT("jpeg");
                    else
                        imageType = ext;
                    mediaType = wxString(wxT("image/")) + imageType;
                }

                if (!mediaType.IsEmpty())
                {
                    textStream << wxT("        <item id=\"") << id << wxT("\" href=\"") << ebTextToHTML(filename) << wxT("\"\n");
                    textStream << wxT("          media-type=\"") << mediaType << wxT("\" />\n");
                }
            }
        }

        textStream << wxT("    </manifest>\n");

        wxString ncxFileId = MakeIdFromFilename(wxFileNameFromPath(ncxFile));
        if (ncxFileId.IsEmpty())
            ncxFileId = wxT("toc");

        textStream << wxT("    <spine toc=\"") << ncxFileId << wxT("\">\n");
        for (i = 0; i < htmlFiles.GetCount(); i++)
        {
            wxString htmlFileId(MakeIdFromFilename(wxFileNameFromPath(htmlFiles[i])));

            // MobiPocket convention to eliminate HTML cover if have image cover
            if (coverPageId == htmlFileId && m_options.m_generateCoverPage && !m_locations.m_coverImageFile.IsEmpty())
            {
                textStream << wxT("        <itemref idref=\"") << htmlFileId << wxT("\" linear=\"no\" />\n");
            }
            else
            {
                textStream << wxT("        <itemref idref=\"") << htmlFileId << wxT("\" />\n");
            }
        }
        textStream << wxT("    </spine>\n");

        if (m_options.m_generateGuide)
        {
            textStream << wxT("    <guide>\n");

            bool outputCover = false;
            if (CanGenerateCoverPage())
            {
                wxString htmlFilename(generatedInfo.m_coverPageFilename);
                wxString htmlFileId(MakeIdFromFilename(htmlFilename));
                wxString guideType = wxT("cover");
                wxString guideTitle = GetStringFromTable(generatedInfo.m_coverPageTitle);

                // This ensures that MobiPocket can ignore the cover page if there's an image.
                textStream << wxT("        <reference type=\"") << guideType << wxT("\" title=\"") << guideTitle << wxT("\"\n");
                textStream << wxT("            href=\"") << ebTextToHTML(htmlFilename) << wxT("\" />\n");

                outputCover = true;
            }            

            for (i = 0; i < m_locations.m_htmlFiles.GetCount(); i++)
            {
                // We might have assigned 'cover' to one of these, in which case, check if we have
                // already generated it above, and don't output it in the guide again if so.
                const ebHtmlFile& htmlFile = m_locations.m_htmlFiles[i];
                if (!htmlFile.m_guideType.IsEmpty() && htmlFile.m_guideType != wxT("<none>") && !(outputCover && htmlFile.m_guideType == wxT("cover")))
                {
                    wxString htmlFilename(htmlFile.GetRelativeHtmlFile(m_locations));
                    htmlFilename.Replace(wxT("OPS/"), wxEmptyString);
#ifdef __WXMSW__
                    htmlFilename.Replace(wxT("\\"), wxT("/"));
#endif

                    wxString tocTitle = htmlFile.m_tocTitle;

                    ebConvertTextToHTML(tocTitle);

                    textStream << wxT("        <reference type=\"") << htmlFile.m_guideType << wxT("\" title=\"") << tocTitle << wxT("\"\n");
                    textStream << wxT("            href=\"") << ebTextToHTML(htmlFilename) << wxT("\" />\n");
                }
            }

            textStream << wxT("    </guide>\n");
        }

        textStream << wxT("</package>\n");

        if (m_relativeZipFiles.Index(contentFileRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(contentFileRelative);
            m_absoluteZipFiles.Add(contentFileAbsolute);
        }
    }
    else
    {
        Log(wxString::Format(_("Could not create %s."), contentFileAbsolute.c_str()));
        return false;
    }

    return true;
}

// Create NCX table of contents
bool ebEpubGenerator::CreateNCXFile()
{
    wxString ncxFileRelative = wxT("OPS/toc.ncx");
    wxString ncxFileAbsolute = AppendPaths(m_locations.m_workingFolder, ncxFileRelative);

    wxFileOutputStream stream(ncxFileAbsolute);
    if (stream.Ok())
    {
        wxString subject = m_basicInfo.GetSubject();
        wxString title = m_basicInfo.GetTitle();
        wxString description = m_basicInfo.GetDescription();
        wxString creator = m_basicInfo.GetAuthor();
        wxString publisher = m_basicInfo.GetPublisher();
        wxString date = m_basicInfo.GetDate();
        wxString identifier = m_basicInfo.GetId();
        wxString url = m_basicInfo.GetUrl();
        wxString language = m_basicInfo.GetLanguage();

        if (language.IsEmpty())
            language = wxT("en");

        if (identifier.IsEmpty())
        {
            if (!url.IsEmpty())
                identifier = url + wxT("/");
            else if (publisher.IsEmpty())
                identifier = publisher + wxT(" ");
            identifier += title;
            ebConvertTextToHTML(identifier);
        }

        ebConvertTextToHTML(subject);
        ebConvertTextToHTML(title);
        ebConvertTextToHTML(language);
        ebConvertTextToHTML(description);
        ebConvertTextToHTML(creator);
        ebConvertTextToHTML(publisher);
        ebConvertTextToHTML(date);
        //ebConvertTextToHTML(identifier);

        wxTextOutputStream textStream(stream);

        int maxDepth = 1;
        size_t i;
        for (i = 0; i < m_locations.m_htmlFiles.GetCount(); i++)
        {
            if (m_locations.m_htmlFiles[i].m_level > maxDepth)
                maxDepth = m_locations.m_htmlFiles[i].m_level;
        }

        textStream << wxT("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
        textStream << wxT("<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\" ");
        textStream << wxT("\"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\n");

        textStream << wxT("<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" xml:lang=\"") << language << wxT("\" version=\"2005-1\">\n");

        textStream << wxT("    <head>\n");
        textStream << wxT("        <meta name=\"dtb:uid\" content=\"") << identifier << wxT("\" />\n");

        textStream << wxT("        <meta name=\"dtb:depth\" content=\"") << wxString::Format(wxT("%d"), maxDepth) << wxT("\" />\n");
        textStream << wxT("        <meta name=\"dtb:totalPageCount\" content=\"0\" />\n");
        textStream << wxT("        <meta name=\"dtb:maxPageNumber\" content=\"0\" />\n");
        textStream << wxT("    </head>\n\n");

        textStream << wxT("    <docTitle>\n");
        textStream << wxT("        <text>") << title << wxT("</text>\n");
        textStream << wxT("    </docTitle>\n\n");

        textStream << wxT("    <navMap>\n");

        // any generated files - cover, toc.

        ebGeneratedContentInfo generatedInfo;

        int id = 1;

        wxArrayString extraFiles;
        wxArrayString extraFileTitles;

        if (CanGenerateCoverPage())
        {
            extraFiles.Add(generatedInfo.m_coverPageFilename);

            wxString title = GetStringFromTable(generatedInfo.m_coverPageTitle);
            ebConvertTextToHTML(title);

            extraFileTitles.Add(title);
        }

        if (m_options.m_generateTitlePage)
        {
            extraFiles.Add(generatedInfo.m_titlePageFilename);

            wxString title = m_basicInfo.GetTitle();
            ebConvertTextToHTML(title);

            extraFileTitles.Add(title);
        }

        if (m_options.m_generateTOC)
        {
            // user table of contents, for readers that can't use the
            // epub navigation features.
            extraFiles.Add(generatedInfo.m_tocFilename);

            wxString title = GetStringFromTable(generatedInfo.m_tocTitle);
            ebConvertTextToHTML(title);

            extraFileTitles.Add(title);
        }

        // First the generated HTML files
        for (i = 0; i < extraFiles.GetCount(); i++)
        {
            wxString idStr(wxString::Format(wxT("%d"), (int) id));
            wxString htmlFilename(extraFiles[i]);
            wxString title(extraFileTitles[i]);

            textStream << wxT("        <navPoint id=\"navPoint-") << idStr << wxT("\" playOrder=\"") << idStr << wxT("\">\n");
            textStream << wxT("            <navLabel>\n");
            textStream << wxT("                <text>") << title << wxT("</text>\n");
            textStream << wxT("            </navLabel>\n");
            textStream << wxT("            <content src=\"") << ebTextToHTML(htmlFilename) << wxT("\" />\n");
            textStream << wxT("        </navPoint>\n");

            id ++;
        }

        int currentLevel = 0;

        // Next the remaining HTML files
        for (i = 0; i < m_locations.m_htmlFiles.GetCount(); i++)
        {
            wxString idStr(wxString::Format(wxT("%d"), (int) id));
            const ebHtmlFile& htmlFile = m_locations.m_htmlFiles[i];
            wxString htmlFilename(htmlFile.GetRelativeHtmlFile(m_locations));
            if (htmlFile.IsHtmlFile() || htmlFile.IsTextFile())
            {
#ifdef __WXMSW__
                htmlFilename.Replace(wxT("\\"), wxT("/"));
#endif
                wxString title(htmlFile.m_tocTitle);
                ebConvertTextToHTML(title);
                
                if (htmlFile.m_level > currentLevel + 1)
                {
                    Log(wxString::Format(_("Section %s has a level greater than the previous level plus one."), htmlFile.m_tocTitle.c_str()));
                    return false;
                }
                
                if (currentLevel >= htmlFile.m_level)
                {
                    size_t j;
                    for (j = currentLevel; j >= (size_t) htmlFile.m_level; j--)
                    {
                        textStream << IndentString(wxT("        </navPoint>\n"), j);
                    }
                }
                
                wxString indent = IndentString(wxEmptyString, htmlFile.m_level);
                
                textStream << indent << wxT("        <navPoint id=\"navPoint-") << idStr << wxT("\" playOrder=\"") << idStr << wxT("\">\n");
                textStream << indent << wxT("            <navLabel>\n");
                textStream << indent << wxT("                <text>") << title << wxT("</text>\n");
                textStream << indent << wxT("            </navLabel>\n");
                textStream << indent << wxT("            <content src=\"") << ebTextToHTML(htmlFilename) << wxT("\" />\n");
                
                currentLevel = htmlFile.m_level;
            }

            id ++;
        }
        if (currentLevel > 0)
        {
            size_t j;
            for (j = currentLevel; j > 0; j--)
            {
                textStream << IndentString(wxT("        </navPoint>\n"), j);
            }
        }

        textStream << wxT("    </navMap>\n");

        textStream << wxT("</ncx>\n");

        if (m_relativeZipFiles.Index(ncxFileRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(ncxFileRelative);
            m_absoluteZipFiles.Add(ncxFileAbsolute);
        }
    }
    else
    {
        Log(wxString::Format(_("Could not create %s."), ncxFileAbsolute.c_str()));
        return false;
    }

    return true;
}

// Create HTML table of contents
bool ebEpubGenerator::CreateHTMLTOCFile()
{
    if (m_options.m_generateTOC)
    {
        ebGeneratedContentInfo contentInfo;

        wxString tocPageFileRelative = wxString(wxT("OPS/")) + contentInfo.m_tocFilename;
        wxString tocPageFileAbsolute = AppendPaths(m_locations.m_workingFolder, tocPageFileRelative);
        wxString tocPageTitle = GetStringFromTable(contentInfo.m_tocTitle); // Or the book title?
        ebConvertTextToHTML(tocPageTitle);

        {
            wxFileOutputStream stream(tocPageFileAbsolute);
            wxTextOutputStream textStream(stream);

            int flags = 0;
            if (m_options.m_useRTL)
                flags |= ebHTML_CONVERSION_RTL;

            WriteHTMLHead(textStream, tocPageTitle, m_locations.m_cssFile, flags);
            
            wxString titleHeadingStr = GetHeadingString(GetTitleHeadingSize());

            textStream << wxT("<body>\n");
            textStream << wxT("<") << titleHeadingStr << wxT(" class=\"toc_heading\">") << tocPageTitle << wxT("</") << titleHeadingStr << wxT(">\n\n");

            size_t i;
            for (i = 0; i < m_locations.m_htmlFiles.GetCount(); i++)
            {
                const ebHtmlFile& htmlFile = m_locations.m_htmlFiles[i];
                if (htmlFile.m_showInTOC)
                {
                    wxString htmlFilename(htmlFile.GetRelativeHtmlFile(m_locations));
#ifdef __WXMSW__
                    htmlFilename.Replace(wxT("\\"), wxT("/"));
#endif
                    wxString title(htmlFile.m_tocTitle);
                    ebConvertTextToHTML(title);
                    
                    wxString indent;
                    size_t j;
                    for (j = 1; j < (size_t) htmlFile.m_level; j++)
                    {
                        indent += wxT("&nbsp;&nbsp;&nbsp;&nbsp;");
                    }
                    
                    textStream << wxT("<p class=\"p_toc\"");
                    if (m_options.m_useRTL)
                        textStream << wxT(" dir=\"rtl\"");
                    textStream << wxT(">") << indent << wxT("<a href=\"") << ebTextToHTML(htmlFilename) << wxT("\">") << title << wxT("</a></p>") << wxT("\n");
                }
            }

            textStream << wxT("</body>\n</html>\n");
        }

        if (m_relativeZipFiles.Index(tocPageFileRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(tocPageFileRelative);
            m_absoluteZipFiles.Add(tocPageFileAbsolute);
        }
    }

    return true;
}

// Create title page file
bool ebEpubGenerator::CreateTitlePageFile()
{
    if (m_options.m_generateTitlePage)
    {
        ebGeneratedContentInfo contentInfo;

        wxString titlePageFileRelative = wxString(wxT("OPS/")) + contentInfo.m_titlePageFilename;
        wxString titlePageFileAbsolute = AppendPaths(m_locations.m_workingFolder, titlePageFileRelative);
        wxString titlePageTitle = m_basicInfo.GetTitle();

        wxString author = m_basicInfo.GetAuthor();
        wxString contributors = m_basicInfo.GetContributors();
        wxString publisher = m_basicInfo.GetPublisher();
        wxString date = m_basicInfo.GetDate();

        ebConvertTextToHTML(titlePageTitle);
        ebConvertTextToHTML(contributors);
        ebConvertTextToHTML(author);
        ebConvertTextToHTML(publisher);
        ebConvertTextToHTML(date);

        {
            wxFileOutputStream stream(titlePageFileAbsolute);
            wxTextOutputStream textStream(stream);

            int flags = 0;
            if (m_options.m_useRTL)
                flags |= ebHTML_CONVERSION_RTL;

            WriteHTMLHead(textStream, titlePageTitle, m_locations.m_cssFile, flags);
            
            textStream << wxT("<body>\n");
            textStream << wxT("<h1 class=\"center\">") << titlePageTitle << wxT("</h1>\n\n");

            if (!author.IsEmpty())
                textStream << wxT("<h2 class=\"center\">") << author << wxT("</h2>\n\n");
            if (!contributors.IsEmpty())
                textStream << wxT("<h3 class=\"center\">") << GetStringFromTable(wxT("Contributions by")) << wxT(" ") << contributors << wxT("</h3>\n\n");

            if (!publisher.IsEmpty())
                textStream << wxT("<h3 class=\"center\">") << publisher << wxT("</h3>\n\n");
            if (!date.IsEmpty())
                textStream << wxT("<h3 class=\"center\">") << date << wxT("</h3>\n\n");

            textStream << wxT("</body>\n</html>\n");
        }

        if (m_relativeZipFiles.Index(titlePageFileRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(titlePageFileRelative);
            m_absoluteZipFiles.Add(titlePageFileAbsolute);
        }
    }

    return true;
}

// Create cover image
bool ebEpubGenerator::CreateCoverPageFile()
{
    if (CanGenerateCoverPage())
    {
        wxString coverImageFileRelative = wxString(wxT("OPS/")) + ebMakeRelative(m_locations.m_coverImageFile, m_locations.m_projectFolder);
        wxString coverImageFileURL = ebMakeRelative(m_locations.m_coverImageFile, m_locations.m_projectFolder);
        wxString coverImageFileAbsolute = AppendPaths(m_locations.m_workingFolder, coverImageFileRelative);

#ifdef __WXMSW__
        coverImageFileRelative.Replace(wxT("\\"), wxT("/"));
        coverImageFileURL.Replace(wxT("\\"), wxT("/"));
        coverImageFileAbsolute.Replace(wxT("/"), wxT("\\"));
#endif

        if (!CopyFileWithDirCreation(m_locations.m_coverImageFile, coverImageFileAbsolute))
        {
            Log(wxString::Format(_("Could not copy cover image file %s to %s."), m_locations.m_coverImageFile.c_str(), coverImageFileAbsolute.c_str()));
            return false;
        }

        ebGeneratedContentInfo contentInfo;

        wxString coverPageFileRelative = wxString(wxT("OPS/")) + ebMakeRelative(contentInfo.m_coverPageFilename, m_locations.m_projectFolder);
        wxString coverPageFileAbsolute = AppendPaths(m_locations.m_workingFolder, coverPageFileRelative);
        wxString coverPageTitle = GetStringFromTable(contentInfo.m_coverPageTitle); // Or the book title?

        {
            if (!wxDirExists(wxPathOnly(coverPageFileAbsolute)))
                FileUtilities::MkDirRecursively(wxPathOnly(coverPageFileAbsolute));

            wxFileOutputStream stream(coverPageFileAbsolute);
            wxTextOutputStream textStream(stream);

            int flags = 0;
            if (m_options.m_useRTL)
                flags |= ebHTML_CONVERSION_RTL;

            WriteHTMLHead(textStream, coverPageTitle, m_locations.m_cssFile, flags);
            
            textStream << wxT("<body>\n");
            textStream << wxT("<p class=\"center\"");
            if (m_options.m_useRTL)
                textStream << wxT(" dir=\"rtl\"");
            textStream << wxT(">\n");

            if (!m_basicInfo.GetUrl().IsEmpty())
            {
                wxString url(m_basicInfo.GetUrl());
                if (url.Left(7) != wxT("http://"))
                    url = wxT("http://") + url;

                textStream << wxT("<a href=\"") << url << wxT("\">");
            }

            textStream << wxT("<img src=\"") << coverImageFileURL << wxT("\"");
            if (m_options.m_expandCoverImage)
                textStream << wxT(" width=\"100%\"");
            textStream << wxT(" alt=\"Cover\" />");

            if (!m_basicInfo.GetUrl().IsEmpty())
                textStream << wxT("</a>");

            textStream << wxT("\n</p>\n");
            textStream << wxT("</body>\n</html>");
        }

        if (m_relativeZipFiles.Index(coverImageFileRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(coverImageFileRelative);
            m_absoluteZipFiles.Add(coverImageFileAbsolute);
        }

        if (m_relativeZipFiles.Index(coverPageFileRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(coverPageFileRelative);
            m_absoluteZipFiles.Add(coverPageFileAbsolute);
        }
    }

    return true;
}

// Create CSS
bool ebEpubGenerator::CreateCSSFile()
{
    if (!m_locations.m_cssFile.IsEmpty())
    {
        wxString cssFileRelative = wxString(wxT("OPS/")) + ebMakeRelative(m_locations.m_cssFile, m_locations.m_projectFolder);
        wxString cssFileAbsolute = AppendPaths(m_locations.m_workingFolder, cssFileRelative);

        if (m_options.m_generateCSS)
        {
            if (!wxDirExists(wxPathOnly(cssFileAbsolute)))
                FileUtilities::MkDirRecursively(wxPathOnly(cssFileAbsolute));

            wxFileOutputStream stream(m_locations.m_cssFile);
            if (stream.Ok())
            {
                wxTextOutputStream textStream(stream);

                wxString rtl;
                if (m_options.m_useRTL)
                    rtl = wxT("direction:RTL; ");

                textStream << wxT(".center { text-align: center;  text-indent: 0em; }\n");
                textStream << wxT(".right { text-align: right; }\n");
                textStream << wxT(".caption { font-weight: bold; }\n");
                textStream << wxT(".noindent { text-indent: 0em; }\n");
                textStream << wxT(".smallcaps { font-variant: small-caps; }\n");
                textStream << wxT(".small { font-size: small; }\n");
                textStream << wxT(".large { font-size: large; }\n");
                textStream << wxT(".note { font-size: small; text-indent: 0em; }\n");

                textStream << wxT(".chapter_heading { text-align: center; margin-top: 20pt; margin-bottom: 20pt; }\n");
                textStream << wxT(".toc_heading { text-align: center; margin-top: 20pt; margin-bottom: 20pt; }\n");

                int margin = m_options.m_paragraphSpacing/2;
                textStream << wxT(".p_normal { ") << rtl << wxT("text-indent: ") << m_options.m_firstLineIndent << wxT("mm; margin-top: ") << margin << wxT("pt; margin-bottom: ") << margin << wxT("pt;}\n");
                textStream << wxT(".p_toc { ") << rtl << wxT("text-indent: 10mm; margin-top: 10pt; margin-bottom: 10pt;}\n");
            }
        }
        if (wxFileExists(m_locations.m_cssFile))
        {
            if (!CopyFileWithDirCreation(m_locations.m_cssFile, cssFileAbsolute))
            {
                Log(wxString::Format(_("Could not copy CSS file %s to %s."), m_locations.m_cssFile.c_str(), cssFileAbsolute.c_str()));
                return false;
            }
        }

        if (m_relativeZipFiles.Index(cssFileRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(cssFileRelative);
            m_absoluteZipFiles.Add(cssFileAbsolute);
        }
    }

    return true;
}

// Create (in fact just copy) content HTML files
bool ebEpubGenerator::CreateHtmlFiles()
{
    // wxString otherExtensions(wxT(";xpgt;txt;"));
    size_t i;
    for (i = 0; i < m_locations.m_htmlFiles.GetCount(); i++)
    {
        const ebHtmlFile& htmlFile = m_locations.m_htmlFiles[i];
        wxString ext(FileUtilities::GetExtension(htmlFile.m_fileLocation).Lower());

        if (1) // htmlFile.IsHtmlFile() || otherExtensions.Find(wxString(wxT(";")) + ext + wxT(";")) != wxNOT_FOUND)
        {
            wxString htmlFilenameSourceAbsolute, htmlFilenameOnly;
            if (htmlFile.IsHtmlFile() || ext == wxT("txt"))
            {
                htmlFilenameSourceAbsolute = htmlFile.GetAbsoluteHtmlFile(m_locations);
                htmlFilenameOnly = htmlFile.GetRelativeHtmlFile(m_locations);
            }
            else
            {
                htmlFilenameSourceAbsolute = htmlFile.GetAbsoluteFile(m_locations);
                htmlFilenameOnly = htmlFile.GetRelativeFile(m_locations);
            }
            
            wxString htmlFilenameRelative = wxString(wxT("OPS/")) + htmlFilenameOnly;
            wxString htmlFilenameDestinationAbsolute(AppendPaths(m_locations.m_workingFolder, htmlFilenameRelative));
            
            if (!CopyFileWithDirCreation(htmlFilenameSourceAbsolute, htmlFilenameDestinationAbsolute))
            {
                Log(wxString::Format(_("Could not copy %s to %s."), htmlFilenameSourceAbsolute.c_str(), htmlFilenameDestinationAbsolute.c_str()));
                return false;
            }
            
            if (m_relativeZipFiles.Index(htmlFilenameRelative) == wxNOT_FOUND)
            {
                m_relativeZipFiles.Add(htmlFilenameRelative);
                m_absoluteZipFiles.Add(htmlFilenameDestinationAbsolute);
            }
        }
    }
    return true;
}

// Create epub file
bool ebEpubGenerator::CreateEpubFile()
{
    if (!m_options.m_generateEpub)
        return true;

    if (CreateZipArchive(m_locations.m_destinationFile, m_absoluteZipFiles, m_relativeZipFiles, m_options.m_compressionLevel))
    {
        wxString dateStr(wxDateTime::Now().Format());
        Log(wxString::Format(_("Epub compilation successful at %s."), dateStr.c_str()));
        Log(wxString::Format(_("Created %s."), GetFileLocations().m_destinationFile.c_str()));

        return true;
    }
    else
    {
        Log(_("Could not create epub zip file."));
        return false;
    }
}

// Create MobiPocket file
bool ebEpubGenerator::CreateMobiPocketFile()
{
    if (!m_options.m_generateMobiPocket)
        return true;

    if (m_mobigenCommand.IsEmpty())
    {
        Log(_("The mobigen command was empty. MobiPocket file not generated."));
        return false;
    }

    wxString contentFileRelative = wxT("OPS/content.opf");
    wxString contentFileAbsolute = AppendPaths(m_locations.m_workingFolder, contentFileRelative);

    wxString mobiFileAbsolute = contentFileAbsolute;
    FileUtilities::EnsureExtension(mobiFileAbsolute, wxT("mobi"));

    wxString finalMobiFileAbsolute = m_locations.m_destinationFile;
    FileUtilities::EnsureExtension(finalMobiFileAbsolute, wxT("mobi"));

    wxString cmd = m_mobigenCommand + wxT(" \"") + contentFileAbsolute + wxT("\"");
    wxArrayString output, errors;
    wxLogNull logNull;
    wxBusyCursor busy;

    int ret = wxExecute(cmd, output, errors);
    bool success = ret != -1;

    size_t i;
    for (i = 0; i < errors.GetCount(); i++)
    {
        wxString str(errors[i]);
        if (!str.IsEmpty())
        {
            if (str.Find(wxT("Warning(prcgen)")) != wxNOT_FOUND)
                Log(str, ebEPUB_OUTPUT_ERRORS_AND_WARNINGS);
            else if (str.Find(wxT("Error(prcgen)")) != wxNOT_FOUND)
                Log(str, ebEPUB_OUTPUT_ERRORS_ONLY);
            else
                Log(str, ebEPUB_OUTPUT_VERBOSE);
        }
    }
    
    for (i = 0; i < output.GetCount(); i++)
    {
        wxString str(output[i]);
        if (!str.IsEmpty())
        {
            if (str.Find(wxT("Warning(prcgen)")) != wxNOT_FOUND)
                Log(str, ebEPUB_OUTPUT_ERRORS_AND_WARNINGS);
            else if (str.Find(wxT("Error(prcgen)")) != wxNOT_FOUND)
                Log(str, ebEPUB_OUTPUT_ERRORS_ONLY);
            else
                Log(str, ebEPUB_OUTPUT_VERBOSE);
        }
    }

    if (success)
    {
         if (wxFileExists(mobiFileAbsolute))
         {
             if (!wxCopyFile(mobiFileAbsolute, finalMobiFileAbsolute))
                 success = false;
             wxRemoveFile(mobiFileAbsolute);
         }
         else
             success = false;
    }

    if (success)
    {
        wxString dateStr(wxDateTime::Now().Format());
        Log(wxString::Format(_("MobiPocket compilation successful at %s."), dateStr.c_str()));
        Log(wxString::Format(_("Created %s."), mobiFileAbsolute.c_str()));

    }
    else
    {
        Log(_("Could not create MobiPocket file."));
        return false;
    }

    return success;
}

// Get the location to place content HTML files etc. in
wxString ebEpubGenerator::GetContentFolder() const
{
    wxString contentFolder = AppendPaths(m_locations.m_workingFolder, wxT("OPS"));
    return contentFolder;
}

// Get the cover page filename (without the path)
wxString ebEpubGenerator::GetCoverPageFilename() const
{
    ebGeneratedContentInfo generatedInfo;
    return generatedInfo.m_coverPageFilename;
}

// Append paths
wxString ebEpubGenerator::AppendPaths(const wxString& path1, const wxString& path2) const
{
    return FileUtilities::AppendPaths(path1, path2);
}

// Can we generate a cover page?
bool ebEpubGenerator::CanGenerateCoverPage() const
{
    return (m_options.m_generateCoverPage && !m_locations.m_coverImageFile.IsEmpty() && wxFileExists(m_locations.m_coverImageFile));
}

// Create a zip file from actual filenames and corresponding archive names.
bool ebEpubGenerator::CreateZipArchive(const wxString& zipArchiveName, const wxArrayString& filenames, const wxArrayString& archiveNames, int compressionLevel)
{
    // Now copy the files into the archive
    if (wxFileExists(zipArchiveName))
        wxRemoveFile(zipArchiveName);

    wxFFileOutputStream outputStr(zipArchiveName);
    if (!outputStr.Ok())
        return false;

    wxZipOutputStream zipStream(outputStr, compressionLevel);

    bool success = true;

    size_t i;
    for (i = 0; i < filenames.GetCount(); i++)
    {
        wxString filename = filenames[i];
        wxString archiveName = archiveNames[i];
        
        if (wxFileExists(filename))
        {
            wxZipEntry* zipEntry = new wxZipEntry(archiveName);
            if (archiveName == wxT("mimetype"))
                zipEntry->SetMethod(wxZIP_METHOD_STORE);
            else
                zipEntry->SetMethod(wxZIP_METHOD_DEFLATE);

            zipStream.PutNextEntry(zipEntry);

            if (!FileUtilities::CopyFileToStream(zipStream, filename))
            {
                success = false;
                Log(wxString::Format(_("Could not add %s to epub archive."), filename.c_str()));
            }
        }
    }

    zipStream.Close();
    return success;
}

// Save all text files as HTML
bool ebEpubGenerator::ConvertTextFilesToHTML(int flags, wxMBConv& conv)
{
    bool success = true;
    size_t i;
    for (i = 0; i < m_locations.m_htmlFiles.GetCount(); i++)
    {
        ebHtmlFile htmlFile(m_locations.m_htmlFiles[i]);
        wxString ext(FileUtilities::GetExtension(htmlFile.m_fileLocation).Lower());
        if (!htmlFile.IsHtmlFile() && ext == wxT("txt"))
        {
            wxString textFilename = htmlFile.GetAbsoluteFile(m_locations);
            wxString htmlFilename = htmlFile.GetAbsoluteHtmlFile(m_locations);
            wxString title = htmlFile.m_tocTitle;
            ebConvertTextToHTML(title);

            if (m_options.m_useRTL)
                flags |= ebHTML_CONVERSION_RTL;

            if (!ConvertFromTextToHTML(textFilename, htmlFilename, title, m_locations.m_cssFile, flags, conv))
            {
                Log(wxString::Format(_("Could not convert %s to HTML. This is probably an encoding problem."), textFilename.c_str()));
                success = false;
            }
        }
    }

    return success;
}

// Write HTML head
bool ebEpubGenerator::WriteHTMLHead(wxTextOutputStream& textStream, const wxString& title, const wxString& cssFilename, int flags)
{
    textStream << wxT("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
    textStream << wxT("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n");
    textStream << wxT("<html xmlns=\"http://www.w3.org/1999/xhtml\"");
    if (flags & ebHTML_CONVERSION_RTL)
        textStream << wxT(" dir=\"rtl\"");
    textStream << wxT(">\n");
    textStream << wxT("<head>\n");
    textStream << wxT("<meta http-equiv=\"Content-Type\" content=\"application/xhtml+xml; charset=utf-8\" />\n");
    if (!cssFilename.IsEmpty())
    {
        textStream << wxT("<link rel=\"stylesheet\" type=\"text/css\" href=\"") << ebTextToHTML(wxFileNameFromPath(cssFilename)) << wxT("\" />\n");
    }
    
    textStream << wxT("<title>") << title << wxT("</title>\n");
    textStream << wxT("</head>\n");

    return true;
}

// Get all image files in the project folder
bool ebEpubGenerator::GetImageFiles()
{
    return GetFilesUnder(m_imageFiles, m_locations.m_projectFolder, m_locations.m_workingFolder, wxT(";png;jpg;jpeg;tif;tiff;gif;svg;ico;bmp;"));

    return true;
}

// Copy image files
bool ebEpubGenerator::CopyImageFiles()
{
    size_t i;
    for (i = 0; i < m_imageFiles.GetCount(); i++)
    {
        wxString imageFilenameSourceAbsolute = m_imageFiles[i];
        
        wxString imageFilenameOnly(ebMakeRelative(imageFilenameSourceAbsolute, m_locations.m_projectFolder));

        wxString imageFilenameRelative = wxString(wxT("OPS/")) + imageFilenameOnly;
        wxString imageFilenameDestinationAbsolute(AppendPaths(m_locations.m_workingFolder, imageFilenameRelative));

#ifdef __WXMSW__
        imageFilenameRelative.Replace(wxT("\\"), wxT("/"));
        imageFilenameDestinationAbsolute.Replace(wxT("/"), wxT("\\"));
#endif

        if (!CopyFileWithDirCreation(imageFilenameSourceAbsolute, imageFilenameDestinationAbsolute))
        {
            Log(wxString::Format(_("Could not copy %s to %s."), imageFilenameSourceAbsolute.c_str(), imageFilenameDestinationAbsolute.c_str()));
            return false;
        }

        if (m_relativeZipFiles.Index(imageFilenameRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(imageFilenameRelative);
            m_absoluteZipFiles.Add(imageFilenameDestinationAbsolute);
        }
    }

    return true;
}

// Copy other files that might be relevant
bool ebEpubGenerator::CopyOtherFiles()
{
    wxString otherExtensions(wxT(";xpgt;"));

    wxArrayString otherFiles;
    GetFilesUnder(otherFiles, m_locations.m_projectFolder, m_locations.m_workingFolder, otherExtensions);

    size_t i;
    for (i = 0; i < otherFiles.GetCount(); i++)
    {
        wxString f(otherFiles[i]);

        wxString otherFilenameOnly(ebMakeRelative(f, m_locations.m_projectFolder));
        wxString otherFilenameRelative = wxString(wxT("OPS/")) + otherFilenameOnly;
        wxString otherFilenameDestinationAbsolute(AppendPaths(m_locations.m_workingFolder, otherFilenameRelative));

#ifdef __WXMSW__
        otherFilenameRelative.Replace(wxT("\\"), wxT("/"));
        otherFilenameDestinationAbsolute.Replace(wxT("/"), wxT("\\"));
#endif

        if (!CopyFileWithDirCreation(f, otherFilenameDestinationAbsolute))
        {
            Log(wxString::Format(_("Could not copy %s to %s."), f.c_str(), otherFilenameDestinationAbsolute.c_str()));
            return false;
        }

        if (m_relativeZipFiles.Index(otherFilenameRelative) == wxNOT_FOUND)
        {
            m_relativeZipFiles.Add(otherFilenameRelative);
            m_absoluteZipFiles.Add(otherFilenameDestinationAbsolute);
        }
    }

    return true;
}

// Get a line with the space trimmed from it, and also, if the first line,
// the UTF-8 marker removed.
static wxString ebGetTrimmedLine(wxTextFile& textFile, size_t i)
{
    wxString line(textFile[i]);
    if (i == 0 && line[0] == (wxChar) 0xFEFF)
    {
        line = line.Mid(1);
    }
    line.Trim(true);
    line.Trim(false);

    return line;
}

// Convert from a text file to an HTML file
bool ebEpubGenerator::ConvertFromTextToHTML(const wxString& textFile, const wxString& htmlFile, wxString& title, const wxString& cssFile, int flags, wxMBConv& conv)
{
    if (wxFileExists(textFile))
    {
        wxFileOutputStream outputStream(htmlFile);
        wxTextOutputStream textStream(outputStream);
        if (outputStream.Ok())
        {
            wxTextFile text(textFile);
            if (text.Open(conv))
            {
                int l = 0;
                while (l < (int) text.GetLineCount())
                {
                    wxString line = ebGetTrimmedLine(text, l);
                    if (!line.IsEmpty())
                        break;
                    else
                        l ++;
                }

                size_t startFrom = l;

                if ((flags & ebHTML_CONVERSION_FIRST_LINE_IS_TITLE) && l < (int) (text.GetLineCount()-1))
                {
                    title = ebGetTrimmedLine(text, l);

                    title.Trim(true);
                    title.Trim(false);

                    startFrom ++;
                }

                if (title.IsEmpty())
                {
                    title = wxFileNameFromPath(textFile);
                    FileUtilities::StripExtension(title);
                }

                if ((flags & ebHTML_CONVERSION_ALLOW_HTML_IN_TEXT) == 0)
                    ebConvertTextToHTML(title);

                WriteHTMLHead(textStream, title, cssFile, flags);

                wxString titleHeadingStr = GetHeadingString(GetTitleHeadingSize());

                textStream << wxT("<body>\n");
                textStream << wxT("<") << titleHeadingStr << wxT(" class=\"chapter_heading\">") << title << wxT("</") << titleHeadingStr << wxT(">\n\n");
                
                size_t i;
                for (i = startFrom; i < text.GetLineCount(); i++)
                {
                    wxString line = ebGetTrimmedLine(text, i);

                    if (!line.IsEmpty())
                    {
                        if ((flags & ebHTML_CONVERSION_ALLOW_HTML_IN_TEXT) == 0)
                            ebConvertTextToHTML(line);

                        // See http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references

                        // Left double quotes
                        //line.Replace(ebUnicodeString(0x201C), wxT("&ldquo;");
                        //line.Replace(ebUnicodeString(0x201D), wxT("&rdquo;");

                        textStream << wxT("<p class=\"p_normal\"");
                        if (flags & ebHTML_CONVERSION_RTL)
                            textStream << wxT(" dir=\"rtl\"");
                        textStream << wxT(">") << line << wxT("</p>\n");
                    }
                }

                textStream << wxT("</body>\n");
                textStream << wxT("</html>\n");

                return true;
            }
            else
                return false;
        }
        else
            return false;
    }
    else
        return false;
}

// Get the title from the first line of the file
bool ebEpubGenerator::GetTextFileTitle(const wxString& textFile, wxString& title, wxMBConv& conv)
{
    if (wxFileExists(textFile))
    {
        wxTextFile text(textFile);
        if (text.Open(conv))
        {
            int l = 0;
            while (l < (int) text.GetLineCount())
            {
                wxString line = ebGetTrimmedLine(text, l);
                if (!line.IsEmpty())
                    break;
                else
                    l ++;
            }
            
            if (l < (int) (text.GetLineCount()-1))
            {
                title = ebGetTrimmedLine(text, l);
                
                title.Trim(true);
                title.Trim(false);
                
                return true;
            }
            else
                return false;
        }
        else
            return false;
    }
    else
        return false;
}

// Indent string according to level
wxString ebEpubGenerator::IndentString(const wxString& str, int level, int spacesPerLevel)
{
    wxString s(str);
    if (level > 1)
        s.Pad(level * spacesPerLevel, wxT(' '), false /* from left */);
    return s;
}

// Copy with creation of directories if necessary
bool ebEpubGenerator::CopyFileWithDirCreation(const wxString& file1, const wxString& file2)
{
    wxString p2(wxPathOnly(file2));

    if (!wxDirExists(p2))
        FileUtilities::MkDirRecursively(p2);

    return FileUtilities::CopyFile(file1, file2);
}

// Get title heading size (h1, h2...)
int ebEpubGenerator::GetTitleHeadingSize()
{
    return 3;
}

wxString ebEpubGenerator::GetHeadingString(int size)
{
    return wxString::Format(wxT("h%d"), size);
}

// Convert from existing e-book, copying files to the project folder
bool ebEpubGenerator::ConvertFromExistingEbook(const wxString& ebookFilename, const wxString& utilityDir, wxArrayString& msgs)
{
    if (!wxFileExists(ebookFilename))
    {
        msgs.Add(wxString::Format(_("Sorry, %s\ncould not be found."), ebookFilename.c_str()));
        return false;
    }

    wxString tempFolder = FileUtilities::AppendPaths(m_locations.m_projectFolder, wxT("tempoeb"));
    if (wxDirExists(tempFolder))
        FileUtilities::RemoveDirRecursively(tempFolder);
    FileUtilities::MkDirRecursively(tempFolder);    

    wxString cmd;
    wxString ext = FileUtilities::GetExtension(ebookFilename).Lower();
    if (ext == wxT("epub"))
    {
        if (!ArchiveUtilities::UnzipTo(ebookFilename, tempFolder))
        {
            msgs.Add(wxString::Format(_("Sorry, there was a problem unarchiving the epub file.")));
            return false;
        }
    }
    else
    {
        if (!wxDirExists(utilityDir))
        {
            msgs.Add(wxString::Format(_("Sorry, the utility directory %s\ncould not be found."), utilityDir.c_str()));
            return false;
        }

        if (ext == wxT("lit"))
            cmd = wxT("lit2oeb");
        else if (ext == wxT("mobi") || ext == wxT("prc"))
            cmd = wxT("mobi2oeb");
        else if (ext == wxT("htm") || ext == wxT("html") || ext == wxT("xhtml"))
            cmd = wxT("html2oeb");

        if (cmd.IsEmpty())
        {
            msgs.Add(wxString::Format(_("Sorry, %s\nhas an extension of unknown type."), ebookFilename.c_str()));
            return false;
        }

        // Run converter
        wxString fullCmd = wxT("\"") + utilityDir + wxFILE_SEP_PATH + cmd + wxT("\" -o \"") + tempFolder + wxT("\" \"") + ebookFilename + wxT("\"");
        wxArrayString errors, output;
        int res = wxExecute(fullCmd, errors, output);
        if (res == -1 || errors.GetCount() > 0)
        {
            size_t i;
            for (i = 0; i < output.GetCount(); i++)
                msgs.Add(output[i]);
            for (i = 0; i < errors.GetCount(); i++)
                msgs.Add(errors[i]);
        }
    }

    // find and parse contents file(s)

    wxArrayString matchingFiles;
    if (wxDir::GetAllFiles(tempFolder, & matchingFiles, wxT("*.opf")) > 0)
    {
        wxString opfFilename = matchingFiles[0];
        wxString contentFolder = wxPathOnly(opfFilename);

        wxString coverImageFilename, coverPageFilename;
        ebFileLocations locations;

        ebEpubParser parser;
        if (parser.ReadOPF(opfFilename, m_basicInfo, m_locations, coverImageFilename, coverPageFilename))
        {
            if (!coverImageFilename.IsEmpty())
            {
                m_locations.m_coverImageFile = coverImageFilename;
            }
            if (!coverPageFilename.IsEmpty())
            {
                // TODO: move this to m_locations
                // Or may be not - just get cover page filename from
                // HTML file with "cover" type.

                // m_generatedContent.m_coverPageFile = coverPageFilename;
            }

            // Copy all content files, then remove non-HTML file names from m_locations and fix
            // up other paths.

            size_t i = 0;
            while (i < m_locations.m_htmlFiles.GetCount())
            {
                ebHtmlFile& file = m_locations.m_htmlFiles[i];
                wxString filename(file.m_fileLocation);

                bool doCopy = false;
                bool isHtml = false;

                wxString sourceFilename;
                wxString destFilename;

                wxString ext = FileUtilities::GetExtension(filename).Lower();
                if (ext == wxT("htm") || ext == wxT("html") || ext == wxT("xhtml"))
                {
                    doCopy = true;
                    isHtml = true;
                    sourceFilename = FileUtilities::AppendPaths(contentFolder, filename);
                    destFilename = FileUtilities::AppendPaths(m_locations.m_projectFolder, filename);

                    wxString title = ebFindTitle(sourceFilename);
                    if (title.IsEmpty())
                        title = file.m_tocTitle;

                    file.m_tocTitle = title;
                    file.m_showInTOC = true;

                    i ++;
                }
                else if (ext == wxT("ncx"))
                {
                    doCopy = false;

                    m_locations.m_htmlFiles.RemoveAt(i);
                }
                else
                {
                    doCopy = true;
                    sourceFilename = FileUtilities::AppendPaths(contentFolder, filename);
                    destFilename = FileUtilities::AppendPaths(m_locations.m_projectFolder, filename);
                    if (ext == wxT("css"))
                        m_locations.m_cssFile = filename;

                    m_locations.m_htmlFiles.RemoveAt(i);
                }

                if (doCopy)
                {
                    CopyFileWithDirCreation(sourceFilename, destFilename);

                    if (isHtml)
                    {
                        wxLogNull logNull;

                        wxString encoding = FileUtilities::FindHtmlEncoding(destFilename);
                        if (encoding.IsEmpty())
                            encoding = wxT("utf-8");

                        wxCSConv conv(encoding);

                        // Must prepend XML tag if missing
                        wxTextFile textFile;
                        if (textFile.Open(destFilename, conv) && textFile.GetLineCount() > 0)
                        {
                            if (textFile.GetLine(0).Find(wxT("<?xml")) == wxNOT_FOUND)
                            {
                                textFile.InsertLine(wxT("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"), 0);
                                textFile.InsertLine(wxT("<?xml version=\"1.0\" encoding=\"") + encoding + wxT("\" ?>"), 0);

                                textFile.Write();
                            }
                        }
                    }
                }
            }
        }
    }

    // Restore after debugging
#if 1 // ndef __WXDEBUG__
    if (wxDirExists(tempFolder))
        FileUtilities::RemoveDirRecursively(tempFolder);
#endif

    return true;
}

// Get all files under the given directory, excluding another dir, matching the given extensions.
bool ebEpubGenerator::GetFilesUnder(wxArrayString& files, const wxString& folder, const wxString& excludingDir, const wxString& exts)
{
    return GetFilesUnder(files, folder, folder, excludingDir, exts);
}

bool ebEpubGenerator::GetFilesUnder(wxArrayString& files, const wxString& topDir, const wxString& folder, const wxString& excludingDir, const wxString& exts)
{
    wxString excl(excludingDir);
    if (!wxIsAbsolutePath(excl))
        excl = FileUtilities::AppendPaths(topDir, excludingDir);

    wxArrayString dirs;
    wxDir dir(folder);
    if (dir.IsOpened())
    {
        wxString filename;
        
        bool cont = dir.GetFirst(&filename, wxT("*"), wxDIR_DIRS);
        while ( cont )
        {
            wxString d = FileUtilities::AppendPaths(folder, filename);
            if (d != excl)
            {
                dirs.Add(d);
            }
            
            cont = dir.GetNext(&filename);
        }
    }

    wxArrayString files1;
    wxDir::GetAllFiles(folder, & files1, wxT("*.*"), wxDIR_FILES);
    size_t i;
    for (i = 0; i < files1.GetCount(); i++)
    {
        wxString f(files1[i]);
        wxString ext = wxT(";") + FileUtilities::GetExtension(f).Lower() + wxT(";");
        if (exts.Find(ext) != wxNOT_FOUND)
            files.Add(f);
    }

    for (i = 0; i < dirs.GetCount(); i++)
    {
        wxString d(dirs[i]);
        GetFilesUnder(files, topDir, d, excludingDir, exts);
    }

    return true;
}

// Make suitable id from filename
wxString ebEpubGenerator::MakeIdFromFilename(const wxString& filename)
{
    wxString id(filename);
    FileUtilities::StripExtension(id);
    id.Replace(wxT("\\"), wxT("_"));
    id.Replace(wxT("/"), wxT("_"));
    id.Replace(wxT(" "), wxT(""));
    id.Replace(wxT("&"), wxT(""));
    return id;
}

// Get a string from the string table, or return itself
wxString ebEpubGenerator::GetStringFromTable(const wxString& name) const
{
    if (m_stringTable)
        return m_stringTable->GetString(name);
    else
        return name;
}

// Add standard instances and attributes for creator
void ebEpubGenerator::AddCreatorAttributes(ebElement& element, bool onlyResetChoices)
{
    wxArrayString roleChoices;
    roleChoices.Add(wxT("aut"));
    roleChoices.Add(wxT("ill"));
        
    if (onlyResetChoices)
    {
        size_t i;
        for (i = 0; i < element.GetCount(); i++)
        {
            ebElementInstance& inst = element.GetInstance(i);
            size_t j;
            for (j = 0; j < inst.GetCount(); j++)
            {
                EpProperty* prop = inst.GetProperties().GetProperty(j);
                if (prop->GetName() == wxT("opf:role"))
                    prop->SetChoices(roleChoices);
            }
        }
    }
    else
    {
        element.Clear();
        
        ebElementInstance inst_1;
        inst_1.SetAttributeValue(wxT("Value"), wxEmptyString);
        inst_1.SetAttributeValue(wxT("opf:role"), wxT("aut"), roleChoices);
        inst_1.SetAttributeValue(wxT("opf:file-as"), wxT(""));
        element.AddInstance(inst_1);
        
        ebElementInstance inst_2;
        inst_2.SetAttributeValue(wxT("Value"), wxEmptyString);
        inst_2.SetAttributeValue(wxT("opf:role"), wxT("ill"), roleChoices);
        inst_2.SetAttributeValue(wxT("opf:file-as"), wxT(""));
        element.AddInstance(inst_2);
    }
}

// Add standard instances and attributes for contributor
void ebEpubGenerator::AddContributorAttributes(ebElement& element, bool onlyResetChoices)
{
    wxArrayString roleChoices;
    roleChoices.Add(wxT("edt")); // editor
    roleChoices.Add(wxT("mdc")); // ??
    roleChoices.Add(wxT("trc")); // ??
    roleChoices.Add(wxT("bkp")); // book producer

    if (onlyResetChoices)
    {
        size_t i;
        for (i = 0; i < element.GetCount(); i++)
        {
            ebElementInstance& inst = element.GetInstance(i);
            size_t j;
            for (j = 0; j < inst.GetCount(); j++)
            {
                EpProperty* prop = inst.GetProperties().GetProperty(j);
                if (prop->GetName() == wxT("opf:role"))
                    prop->SetChoices(roleChoices);
            }
        }
    }
    else
    {
        element.Clear();
        
        ebElementInstance inst_1;
        inst_1.SetAttributeValue(wxT("Value"), wxEmptyString);
        inst_1.SetAttributeValue(wxT("opf:role"), wxT("edt"), roleChoices);
        inst_1.SetAttributeValue(wxT("opf:file-as"), wxT(""));
        element.AddInstance(inst_1);
    }
}

// Add standard instances and attributes for identifier
void ebEpubGenerator::AddIdentifierAttributes(ebElement& element, bool onlyResetChoices)
{
    wxArrayString schemeChoices;
    schemeChoices.Add(wxT("ISBN"));
    schemeChoices.Add(wxT("URN"));
    schemeChoices.Add(wxT("URI"));

    if (onlyResetChoices)
    {
        size_t i;
        for (i = 0; i < element.GetCount(); i++)
        {
            ebElementInstance& inst = element.GetInstance(i);
            size_t j;
            for (j = 0; j < inst.GetCount(); j++)
            {
                EpProperty* prop = inst.GetProperties().GetProperty(j);
                if (prop->GetName() == wxT("opf:scheme"))
                    prop->SetChoices(schemeChoices);
            }
        }
    }
    else
    {
        element.Clear();
        
        ebElementInstance inst_1;
        inst_1.SetAttributeValue(wxT("Value"), wxEmptyString);
        inst_1.SetAttributeValue(wxT("opf:scheme"), wxT("URI"), schemeChoices);
        inst_1.SetAttributeValue(wxT("id"), wxT("BookId"));
        element.AddInstance(inst_1);
    }
}

// Add standard instances and attributes for publisher
void ebEpubGenerator::AddPublisherAttributes(ebElement& WXUNUSED(element), bool WXUNUSED(onlyResetChoices))
{
}

// Add standard instances and attributes for date
void ebEpubGenerator::AddDateAttributes(ebElement& element, bool onlyResetChoices)
{
    wxArrayString dctermsChoices;
    dctermsChoices.Add(wxT("dcterms:W3CDTF"));

    if (onlyResetChoices)
    {
        size_t i;
        for (i = 0; i < element.GetCount(); i++)
        {
            ebElementInstance& inst = element.GetInstance(i);
            size_t j;
            for (j = 0; j < inst.GetCount(); j++)
            {
                EpProperty* prop = inst.GetProperties().GetProperty(j);
                if (prop->GetName() == wxT("xsi:type"))
                    prop->SetChoices(dctermsChoices);
            }
        }
    }
    else
    {
        element.Clear();
        
        ebElementInstance inst_1;
        inst_1.SetAttributeValue(wxT("Value"), wxEmptyString);
        inst_1.SetAttributeValue(wxT("xsi:type"), wxT("dcterms:W3CDTF"), dctermsChoices);
        element.AddInstance(inst_1);
    }
}

// Add standard instances and attributes for language
void ebEpubGenerator::AddLanguageAttributes(ebElement& element, bool onlyResetChoices)
{
    wxArrayString dctermsChoices;
    dctermsChoices.Add(wxT("dcterms:RFC3066"));

    wxArrayString langChoices;

#if 0
    langChoices.Add(wxT("en-US"));
    langChoices.Add(wxT("en-UK"));
#endif

    int i;
    for (i = wxLANGUAGE_UNKNOWN; i < (int) wxLANGUAGE_USER_DEFINED; i++)
    {
        const wxLanguageInfo* info = wxLocale::GetLanguageInfo((wxLanguage) i);
        if (info)
        {
            wxString name = info->Description;
            wxString canonicalName = info->CanonicalName;
            
            if (!canonicalName.IsEmpty())
            {
                canonicalName.Replace(wxT("_"), wxT("-"));
                if (langChoices.Index(canonicalName) == wxNOT_FOUND)
                    langChoices.Add(canonicalName);
            }
        }
    }

    if (onlyResetChoices)
    {
        size_t i;
        for (i = 0; i < element.GetCount(); i++)
        {
            ebElementInstance& inst = element.GetInstance(i);
            size_t j;
            for (j = 0; j < inst.GetCount(); j++)
            {
                EpProperty* prop = inst.GetProperties().GetProperty(j);
                if (prop->GetName() == wxT("Value"))
                    prop->SetChoices(langChoices);
                else if (prop->GetName() == wxT("xsi:type"))
                    prop->SetChoices(dctermsChoices);
            }
        }
    }
    else
    {
        element.Clear();
        
        ebElementInstance inst_1;
        inst_1.SetAttributeValue(wxT("Value"), wxEmptyString, langChoices);
        inst_1.SetAttributeValue(wxT("xsi:type"), wxT("dcterms:RFC3066"), dctermsChoices);
        element.AddInstance(inst_1);
    }
}

// Type:
// http://dublincore.org/documents/2000/07/11/dcmi-type-vocabulary/
// http://dublincore.org/documents/usageguide/elements.shtml
// See also:
// http://www.mobileread.com/forums/archive/index.php?t-33192.html

#if 0
// Add standard instances and attributes for type
void ebEpubGenerator::AddTypeAttributes(ebElement& element, bool onlyResetChoices)
{
    wxArrayString typeChoices;
    dctermsChoices.Add(wxT("dcterms:W3CDTF"));

    if (onlyResetChoices)
    {
        size_t i;
        for (i = 0; i < element.GetCount(); i++)
        {
            ebElementInstance& inst = element.GetInstance(i);
            size_t j;
            for (j = 0; j < inst.GetCount(); j++)
            {
                EpProperty* prop = inst.GetProperties().GetProperty(j);
                if (prop->GetName() == wxT("xsi:type"))
                    prop->SetChoices(dctermsChoices);
            }
        }
    }
    else
    {
        element.Clear();
        
        ebElementInstance inst_1;
        inst_1.SetAttributeValue(wxT("Value"), wxEmptyString);
        inst_1.SetAttributeValue(wxT("xsi:type"), wxT("dcterms:W3CDTF"), dctermsChoices);
        element.AddInstance(inst_1);
    }
}
#endif

/*
 * .epub parser
 */

void ebEpubParser::Init()
{
}

// Read an element, potentially with multiple instances, together with all attributes
bool ebEpubParser::ReadElement(ebElement& element, const wxString& elementName, wxXmlNode* node)
{
    element.Clear();
    element.SetName(elementName);

    wxXmlNode *n = node->GetChildren();
    
    while (n)
    {
        if (n->GetType() == wxXML_ELEMENT_NODE && n->GetName() == elementName)
        {
            ebElementInstance inst;

            // Make sure this is the first attribute
            inst.SetAttributeValue(wxT("Value"), wxEmptyString);

            wxXmlProperty* prop = n->GetProperties();
            while (prop)
            {
                inst.SetAttributeValue(prop->GetName(), prop->GetValue());
                prop = prop->GetNext();
            }

            wxString value = GetText(n, wxEmptyString);
            value.Trim(true);
            value.Trim(false);

            inst.SetAttributeValue(wxT("Value"), value);

            element.AddInstance(inst);
        }
        n = n->GetNext();
    }

    return true;
}

// Extracts the OPF spec to a file.
bool ebEpubParser::ExtractOPFToFile(const wxString& epubFilename, wxString& opfFilename, wxString& opfLocationInZip)
{
    wxFFileInputStream inputStr(epubFilename);
    if (!inputStr.Ok())
        return false;

    wxString tempOpfFilename;

    bool success = false;

    wxZipInputStream zipStr(inputStr);
    wxZipEntry* zipEntry = NULL;
    while (true)
    {
        if (zipEntry)
        {
            delete zipEntry;
        }
        zipEntry = zipStr.GetNextEntry();
        if (!zipEntry)
            break;
        else
        {
            wxString zipFile = zipEntry->GetInternalName();

            wxString ext = wxFileName(zipFile).GetExt().Lower();
            if (ext == wxT("opf"))
            {
                opfLocationInZip = zipFile;

                zipStr.OpenEntry(*zipEntry);
                int len = zipStr.GetSize();
                
                char* buf = new char[len + 1];
                zipStr.Read(buf, len);
                buf[len] = 0;

                // Write to temporary file
                wxFile file;
                tempOpfFilename = wxFileName::CreateTempFileName(wxT("opf"), & file);
                if (!tempOpfFilename.IsEmpty())
                {
                    file.Write(buf, len);

                    success = true;
                }

                delete zipEntry;
                break;
            }
        }
    }

    if (!success && wxFileExists(tempOpfFilename))
        wxRemoveFile(tempOpfFilename);
    else
        opfFilename = tempOpfFilename;

    return success;
}

// Reads the basic information about a book
bool ebEpubParser::ReadBasicInfo(const wxString& filename, ebBasicInfo& info, wxString& coverImageZipName)
{
    wxFFileInputStream inputStr(filename);
    if (!inputStr.Ok())
        return false;

    wxString tempOpfFilename;
    wxString opfLocation;

    if (ExtractOPFToFile(filename, tempOpfFilename, opfLocation))
    {
        wxFileInputStream stream(tempOpfFilename);
        
        wxString coverImageFilename, coverPageFilename;
        ebFileLocations locations;

        if (ReadOPF(stream, info, locations, coverImageFilename, coverPageFilename))
        {
            if (!coverImageFilename.IsEmpty())
            {
                coverImageZipName = wxPathOnly(opfLocation) + wxT("/") + coverImageFilename;
            }
        }        
        
        wxRemoveFile(tempOpfFilename);
    }

    return true;
}

// Reads advanced information about a book
bool ebEpubParser::ReadAdvancedInfo(const wxString& filename, ebBasicInfo& info, ebFileLocations& locations, wxString& opfLocation, wxString& coverImageFilename, wxString& coverPageFilename)
{

    wxFFileInputStream inputStr(filename);
    if (!inputStr.Ok())
        return false;

    wxString tempOpfFilename;

    if (ExtractOPFToFile(filename, tempOpfFilename, opfLocation))
    {
        wxFileInputStream stream(tempOpfFilename);
        
        if (ReadOPF(stream, info, locations, coverImageFilename, coverPageFilename))
        {
        }        
        
        wxRemoveFile(tempOpfFilename);
    }

    return true;
}


// Reads the OPF information
bool ebEpubParser::ReadOPF(wxInputStream& stream, ebBasicInfo& info, ebFileLocations& locations, wxString& coverImageFilename, wxString& coverPageFilename)
{
    wxXmlDocument* xmlDoc = new wxXmlDocument;
    if (xmlDoc->Load(stream))
    {
        wxXmlNode* parent = xmlDoc->GetRoot();
        wxXmlNode* node = GetParamNode(parent, wxT("metadata"));
        if (node)
        {
            ReadElement(info.m_title, wxT("dc:title"), node);

            ReadElement(info.m_author, wxT("dc:creator"), node);
            ebEpubGenerator::AddCreatorAttributes(info.m_author, true /* just reset choices */);

            ReadElement(info.m_publisher, wxT("dc:publisher"), node);
            ebEpubGenerator::AddPublisherAttributes(info.m_publisher, true /* just reset choices */);

            ReadElement(info.m_contributors, wxT("dc:contributor"), node);
            ebEpubGenerator::AddContributorAttributes(info.m_contributors, true /* just reset choices */);
            
            ReadElement(info.m_date, wxT("dc:date"), node);
            ebEpubGenerator::AddDateAttributes(info.m_date, true /* just reset choices */);
            
            ReadElement(info.m_subject, wxT("dc:subject"), node);
            
            ReadElement(info.m_description, wxT("dc:description"), node);
            
            ReadElement(info.m_rights, wxT("dc:rights"), node);
            
            ReadElement(info.m_id, wxT("dc:identifier"), node);
            ebEpubGenerator::AddIdentifierAttributes(info.m_id, true /* just reset choices */);
            
            ReadElement(info.m_language, wxT("dc:language"), node);
            ebEpubGenerator::AddLanguageAttributes(info.m_language, true /* just reset choices */);

            ReadElement(info.m_coverage, wxT("dc:coverage"), node);

            ReadElement(info.m_type, wxT("dc:type"), node);
            
            ReadElement(info.m_format, wxT("dc:format"), node);
            
            ReadElement(info.m_source, wxT("dc:source"), node);
            
            ReadElement(info.m_relation, wxT("dc:relation"), node);

        }
        
        wxString coverId;
        
        if (node)
        {
            wxXmlNode* metaNode = GetParamNode(node, wxT("meta"));
            if (metaNode)
            {
                wxString nameParam, idParam;
                nameParam = metaNode->GetPropVal(wxT("name"), wxEmptyString);
                if (nameParam == wxT("cover"))
                    coverId = metaNode->GetPropVal(wxT("content"), wxEmptyString);
            }
        }
        
        wxArrayString manifestFileNames;
        wxArrayString manifestFileIds;
        
        // Try to find cover image in manifest
        
        // We'll add all files in the manifest to the HTML files array,
        // even non-HTML files, and we can sort them out in the calling code.
        locations.m_htmlFiles.Clear();

        wxXmlNode* manifestNode = GetParamNode(parent, wxT("manifest"));
        if (manifestNode)
        {
            wxXmlNode* itemNode = manifestNode->GetChildren();
            while (itemNode)
            {
                if (itemNode->GetName() == wxT("item"))
                {
                    wxString filename = itemNode->GetPropVal(wxT("href"), wxEmptyString);
                    wxString id = itemNode->GetPropVal(wxT("id"), wxEmptyString);
                    
                    if (!filename.IsEmpty() && !id.IsEmpty())
                    {
                        manifestFileNames.Add(filename);
                        manifestFileIds.Add(id);

                        ebHtmlFile file;
                        file.m_fileLocation = filename;
                        file.m_tocTitle = id;

                        locations.m_htmlFiles.Add(file);
                    }
                }
                
                itemNode = itemNode->GetNext();
            }
        }
        
        size_t i;
        for (i = 0; i < manifestFileNames.GetCount(); i++)
        {
            wxString manifestFilename(manifestFileNames[i]);
            wxString manifestId(manifestFileIds[i]);
            
            if (!coverId.IsEmpty())
            {
                if (manifestId == coverId)
                {
                    coverImageFilename = manifestFilename;
                    break;
                }
            }
            else
            {
                if (manifestFilename.Lower().Contains(wxT("cover")))
                {
                    coverImageFilename = manifestFilename;
                    break;
                }
            }
        }

        /*
        TODO: find coverPageFilename from guide:
        <guide>
          <reference type="cover" title="Cover Page"
              href="CoverPage.html" />
        </guide>
        */
        wxXmlNode* guideNode = GetParamNode(parent, wxT("guide"));
        if (guideNode)
        {
            wxXmlNode* itemNode = guideNode->GetChildren();
            while (itemNode)
            {
                if (itemNode->GetName() == wxT("reference"))
                {
                    wxString filename = itemNode->GetPropVal(wxT("href"), wxEmptyString);
                    wxString type = itemNode->GetPropVal(wxT("type"), wxEmptyString);
                    wxString title = itemNode->GetPropVal(wxT("title"), wxEmptyString);
                    
                    if (type == wxT("cover"))
                        coverPageFilename = filename;

                    int idx = locations.FindHtmlFileByFilename(filename);
                    if (idx != -1)
                    {
                        locations.m_htmlFiles[idx].m_guideType = type;
                        locations.m_htmlFiles[idx].m_tocTitle = title;
                        locations.m_htmlFiles[idx].m_showInTOC = true;
                    }
                }
                
                itemNode = itemNode->GetNext();
            }
        }
    }

    return true;
}

bool ebEpubParser::ReadOPF(const wxString& filename, ebBasicInfo& info, ebFileLocations& locations, wxString& coverImageFilename, wxString& coverPageFilename)
{
    wxFileInputStream stream(filename);
    return ReadOPF(stream, info, locations, coverImageFilename, coverPageFilename);
}

// Extract a file from zip archive, to the given file.
bool ebEpubParser::ExtractFileFromArchive(const wxString& archiveName, const wxString& sourceFilename, const wxString& destFilename)
{
    wxFFileInputStream inputStr(archiveName);
    if (!inputStr.Ok())
        return false;

    bool success = false;

    wxZipInputStream zipStr(inputStr);
    wxZipEntry* zipEntry = NULL;
    while (true)
    {
        if (zipEntry)
        {
            delete zipEntry;
        }
        zipEntry = zipStr.GetNextEntry();
        if (!zipEntry)
            break;
        else
        {
            wxString zipFile = zipEntry->GetInternalName();

            if (sourceFilename == zipFile)
            {
                zipStr.OpenEntry(*zipEntry);
                int len = zipStr.GetSize();
                
                char* buf = new char[len + 1];
                zipStr.Read(buf, len);
                buf[len] = 0;

                // Write to temporary file
                wxFile file;
                if (file.Create(destFilename, true /* overwrite */))
                {
                    success = (file.Write(buf, len) != 0) || (len == 0);
                }

                delete zipEntry;
                break;
            }
        }
    }

    return success;
}

// Create an image block from the image file in the given zip archive
bool ebEpubParser::CreateImageBlockFromZipImage(const wxString& archiveName, const wxString& sourceFilename, wxImageBlock& imageBlock, wxImage& image)
{
    if (sourceFilename.IsEmpty())
        return false;

    int imageType = wxImageBlock::DetermineImageType(sourceFilename);
    if (imageType == -1)
        return false;

    wxString tempFilename = wxFileName::CreateTempFileName(wxT("image"));
    if (ExtractFileFromArchive(archiveName, sourceFilename, tempFilename))
    {
        bool success = imageBlock.MakeImageBlock(tempFilename, image, imageType);
        wxRemoveFile(tempFilename);
        return success;
    }

    return false;
}

// Insensitive string search
int ebStringFindInsens(const wxString& source, const wxString& match)
{
    size_t len = source.Length();
    size_t matchLen = match.Length();
    for (size_t i = 0; i < len; i++)
    {
        wxString toMatch(source.Mid(i, matchLen));
        if (match.CmpNoCase(toMatch) == 0)
            return i;
    }
    return -1;
}

// Returns the string between start and end tags if found,
// setting 'str' to the remaining part.
wxString ebFindTag(const wxString& str, const wxString& tag)
{
    wxString retString(wxEmptyString);

    wxString startTag(tag);
    wxString endTag(tag);

    startTag = wxString(_T("<")) + startTag;
    endTag = wxString(_T("</")) + endTag + wxString(_T(">"));

    bool isNormalTag = TRUE; // True if e.g. <P>...</P> else is e.g. <META ... >

    int first = ebStringFindInsens(str, startTag);

    if (first != -1)
    {
        wxString str1 = str.Mid(first + startTag.Length());
        if (str1.GetChar(0) == _T('>'))
        {
            isNormalTag = TRUE;
            str1 = str1.Mid(1);
        }
        else
        {
            isNormalTag = FALSE;
            if (str1.GetChar(0) == _T(' '))
                str1 = str1.Mid(1);
        }

        int last;
        if (isNormalTag)
        {
            last = ebStringFindInsens(str1, endTag);
        }
        else
        {
            last = str1.Find(_T('>'));
        }

        if (last != -1)
        {
            retString = str1.Left(last);

            // Do some white-space trimming
            retString.Trim(FALSE);
            retString.Trim(TRUE);

            while (retString.GetChar(0) == _T('\r') || retString.GetChar(0) == _T('\n'))
                retString = retString.Mid(1);
            while (retString.GetChar(retString.Length()-1) == _T('\r') || retString.GetChar(retString.Length()-1) == _T('\n'))
                retString = retString.Left(retString.Length() - 1);
            
            return retString;
        }
    }
    return retString;
}

// Returns the string between start and end tags if found,
// setting 'str' to the remaining part.
wxString ebFindTag(wxString& str, const wxString& tag)
{
    wxString retString(wxEmptyString);

    wxString startTag(tag);
    wxString endTag(tag);

    startTag = wxString(_T("<")) + startTag;
    endTag = wxString(_T("</")) + endTag + wxString(_T(">"));

    bool isNormalTag = TRUE; // True if e.g. <P>...</P> else is e.g. <META ... >

    int first = ebStringFindInsens(str, startTag);

    if (first != -1)
    {
        wxString str1 = str.Mid(first + startTag.Length());
        if (str1.GetChar(0) == _T('>'))
        {
            isNormalTag = TRUE;
            str1 = str1.Mid(1);
        }
        else
        {
            isNormalTag = FALSE;
            if (str1.GetChar(0) == _T(' '))
                str1 = str1.Mid(1);
        }

        int last;
        if (isNormalTag)
        {
            last = ebStringFindInsens(str1, endTag);
        }
        else
        {
            last = str1.Find(_T('>'));
        }

        if (last != -1)
        {
            retString = str1.Left(last);
            if (isNormalTag)
                str = str1.Mid(last + endTag.Length());
            else
                str = str1.Mid(last + 1);

            // Do some white-space trimming
            retString.Trim(FALSE);
            retString.Trim(TRUE);

            while (retString.GetChar(0) == _T('\r') || retString.GetChar(0) == _T('\n'))
                retString = retString.Mid(1);
            while (retString.GetChar(retString.Length()-1) == _T('\r') || retString.GetChar(retString.Length()-1) == _T('\n'))
                retString = retString.Left(retString.Length() - 1);
            
            return retString;
        }
    }
    return retString;
}    

// Find title for HTML file
wxString ebFindTitle(const wxString& htmlFilename)
{
    wxString htmlString;
    
    // Find the title of the page if possible
    wxTextFile textFile(htmlFilename);
    if (textFile.Open())
    {
        int l;
        for (l = 0; l < (int) textFile.GetLineCount(); l++)
        {
            htmlString += textFile[l];
            htmlString += wxT("\n");
        }
        
        wxString title = ebFindTag(htmlString, wxT("title"));
        if (title.IsEmpty())
        {
            int j;
            for (j = 1; j < 5; j++)
            {
                wxString tagName(wxString::Format(wxT("h%d"), j));
                title = ebFindTag(htmlString, tagName);
                if (!title.IsEmpty())
                    break;
            }
        }
        return title;
    }

    return wxEmptyString;
}

// Return a relative file
wxString ebMakeRelative(const wxString& filename1, const wxString& relativeTo)
{
    wxString filename(filename1);
    if (wxIsAbsolutePath(filename))
    {
        wxFileName fname(filename);
        fname.MakeRelativeTo(relativeTo);
        filename = fname.GetFullPath();

        if (filename.Find(wxT("..")) != wxNOT_FOUND)
            filename = wxFileNameFromPath(filename1);
    }

    return filename;
}


#if ebUSE_COMPLEX_ATTRIBUTES

WX_DEFINE_OBJARRAY(wxVariantArray);

/*
 * ebElementInstance
 * A single instance of an element: consists of attribute/value pairs
 */

void ebElementInstance::Init()
{
}

void ebElementInstance::Copy(const ebElementInstance& inst)
{
    m_attributes = inst.m_attributes;
}

bool ebElementInstance::operator==(const ebElementInstance& inst) const
{
    if (m_attributes.GetCount() != inst.m_attributes.GetCount())
        return false;

    size_t i;
    for (i = 0; i < m_attributes.GetCount(); i++)
        if (!((*this)[i] == inst[i]))
            return false;
    
    return true;
}

void ebElementInstance::SetAttributeValue(const wxString& name, const wxString& value, const wxArrayString& choices)
{
    wxVariant* var = FindAttribute(name);
    if (var)
        (*var) = value;
    else
        m_attributes.AddProperty(new EpProperty(name, value));

    if (choices.GetCount() > 0)
    {
        EpProperty* prop = GetProperties().FindProperty(name);
        if (prop)
            prop->SetChoices(choices);
    }
}

wxVariant ebElementInstance::GetAttributeValue(const wxString& name) const
{
    wxVariant* var = FindAttribute(name);
    if (var)
        return *var;
    else
        return wxNullVariant;
}

bool ebElementInstance::RemoveAttribute(size_t i)
{
    m_attributes.DeleteProperty(i);
    return true;
}

wxVariant* ebElementInstance::FindAttribute(const wxString& name) const
{
    size_t i;
    for (i = 0; i < m_attributes.GetCount(); i++)
        if (((*this)[i].GetName() == name))
            return (wxVariant*) & GetAttribute(i);

    return NULL;
}

wxVariant* ebElementInstance::FindOrCreateAttribute(const wxString& name)
{
    wxVariant* v = FindAttribute(name);
    if (!v)
    {
        m_attributes.AddProperty(new EpProperty(name, wxString()));
        v = (wxVariant*) & GetAttribute(GetCount()-1);
    }
    return v;
}

WX_DEFINE_OBJARRAY(ebElementInstanceArray);

/*
 * ebElement
 * A named element, with one or more instances
 */

void ebElement::Init()
{
    ebElementInstance inst;
    inst.SetAttributeValue(wxT("Value"), wxEmptyString);

    m_instances.Add(inst);
}

void ebElement::Copy(const ebElement& element)
{
    m_instances = element.m_instances;
    m_name = element.m_name;
    m_displayName = element.m_displayName;
}

bool ebElement::operator==(const ebElement& element) const
{
    if (m_name != element.m_name)
        return false;
    if (m_displayName != element.m_displayName)
        return false;
    if (GetCount() != element.GetCount())
        return false;
    
    size_t i;
    for (i = 0 ; i < m_instances.GetCount(); i++)
        if (!(m_instances[i] == element.m_instances[i]))
            return false;

    return true;
}

void ebElement::AddInstance(const ebElementInstance& instance)
{
    m_instances.Add(instance);
}

bool ebElement::RemoveInstance(size_t i)
{
    m_instances.RemoveAt(i);
    return true;
}

// Create at least one instance
void ebElement::InitializeInstances()
{
    if (GetCount() == 0)
    {
        ebElementInstance inst;
        inst.SetAttributeValue(wxT("Value"), wxEmptyString);
        m_instances.Add(inst);
    }
}

// Get default value (Value of first instance)
wxString ebElement::GetDefaultValue() const
{
    if (GetCount() > 0)
    {
        wxVariant v(GetInstance(0).GetAttributeValue(wxT("Value")));
        return v.GetString();
    }
    else
        return wxEmptyString;
}

// Get all non-empty values
wxArrayString ebElement::GetValues() const
{
    wxArrayString values;
    size_t j;
    for (j = 0; j < GetCount(); j++)
    {
        const ebElementInstance& inst = GetInstance(j);
        wxString value(inst.GetAttributeValue(wxT("Value")).GetString());
        if (!value.IsEmpty())
            values.Add(value);
    }

    return values;
}

// Get combined string value from all instances
wxString ebElement::GetCombinedValue(const wxString& separator, bool useAnd) const
{
    wxArrayString values(GetValues());
    if (values.GetCount() == 0)
        return wxEmptyString;
    else
    {
        wxString value;
        size_t i;
        for (i = 0; i < values.GetCount(); i++)
        {
            if ((i > 0) && (i == (values.GetCount()-1)) && useAnd)
                value += wxT(" and ");
            else if ((i > 0) && (i == (values.GetCount()-1)))
            {
                value += separator;
                if (separator != wxT("\n"))
                    value += wxT(" ");
            }

            value += values[i];
        }
        return value;
    }
}

// Get the variant corresponding to the default value
wxVariant* ebElement::GetDefaultValueVariant()
{
    InitializeInstances();

    return GetInstance(0).FindOrCreateAttribute(wxT("Value"));
}

// Set default value (Value of first instance)
void ebElement::SetDefaultValue(const wxString& value)
{
    InitializeInstances();

    GetInstance(0).SetAttributeValue(wxT("Value"), value);
}

// Get the first attribute matching this name
wxString ebElement::GetAttributeValue(const wxString& name) const
{
    size_t j;
    for (j = 0; j < GetCount(); j++)
    {
        const ebElementInstance& inst = GetInstance(j);
        wxString attrValue(inst.GetAttributeValue(name).GetString());
        if (!attrValue.IsEmpty())
            return attrValue;
    }
    return wxEmptyString;
}

// Set the first instance's attribute
void ebElement::SetAttributeValue(const wxString& name, const wxString& value)
{
    if (GetCount() == 0)
        InitializeInstances();

    GetInstance(0).SetAttributeValue(name, value);
}

// Generate XML for this element
wxString ebElement::GenerateXML(const wxString& indent, const wxString& defaultValue) const
{
    wxString s;
    
    if (!GetName().IsEmpty())
    {
        size_t j;
        for (j = 0; j < GetCount(); j++)
        {
            const ebElementInstance& inst = GetInstance(j);
            wxString value(inst.GetAttributeValue(wxT("Value")).GetString());
            if (value.IsEmpty())
                value = defaultValue;

            if (!value.IsEmpty())
            {        
                ebConvertTextToHTML(value);
                
                s << indent << wxT("<") << GetName();
                
                size_t i;
                for (i = 0; i < inst.GetCount(); i++)
                {
                    wxVariant v(inst.GetAttribute(i));
                    
                    wxString name(v.GetName());
                    wxString attrValue(v.GetString());
                    if (!attrValue.IsEmpty() && name != wxT("Value"))
                    {
                        ebConvertTextToHTML(attrValue);

                        s << wxT(" ") << name << wxT("=\"") << attrValue << wxT("\"");
                    }
                }
                
                s << wxT(">");
                
                s << value;
                s << wxT("</") << GetName() << wxT(">\n");
            }
        }
    }

    return s;
}

#endif
    // ebUSE_COMPLEX_ATTRIBUTES

static void ebConvertTextToHTML(wxString& str)
{
    if (!str.Matches(wxT("*&*;*")))
    {
        str.Replace(wxT("&"), wxT("&amp;"));
        str.Replace(wxT("<"), wxT("&lt;"));
        str.Replace(wxT(">"), wxT("&gt;"));
    }
}
